diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..9825a1c --- /dev/null +++ b/.dockerignore @@ -0,0 +1,6 @@ +.* +__pycache_ +docker +test* +output* +# mock_data \ No newline at end of file diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..45fc8c3 --- /dev/null +++ b/.gitignore @@ -0,0 +1,10 @@ +.venv +.vscode +__pycache__ +test* +output* + +UnitePSSMs/UnitePSSMs +PSSM_score_Peptide/PSSM_score_Peptide +hits_cpp/hits +tfidf/tfidf diff --git a/.idea/misc.xml b/.idea/misc.xml old mode 100755 new mode 100644 diff --git a/.idea/modules.xml b/.idea/modules.xml old mode 100755 new mode 100644 diff --git a/.idea/src.iml b/.idea/src.iml old mode 100755 new mode 100644 diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..6f0eb8b --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,34 @@ +# Change Log +All notable changes to this project will be documented in this file. + +### [1.2.0] - 2021-10-05 + +### Changes +- Changed default values of parameters: + - Phase 1: + - Changed maximum_length_required from 12 to 14 + - Calculate rpm by default unless using flag no_calculate_rpm + - Phase 2: + - Changed aln_cutoff from 20 to 24 + - Changed pcc_cutoff from 0.6 to 0.7 + - Changed threshold from 0.5 to 0.6 + - Changed word_length from 2 to 4 + - Changed discard from 1 to 4 + - Phase 3: + - Changed shuffles from 5 to 10 + - Scanning with using rpm faa file unless using no_use_rpm_faa_scanning + - Log the hit sequences while scanning unless using no_output_sequences_scanning +- Changed the position of stop machines AWS to be after create done file. + +### Added +- Added CHANGELOG file. + +### [1.1.0] - 2021-09-22 + +### Added +- Added new file - positive motifs: keep only positive motifs before the random forest. + +### [1.0.0] - 2021-08-01 + +- 🎉 first stable release! + diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..66e72a8 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,46 @@ +FROM ubuntu:18.04 + +RUN apt update -y && \ + apt install -y python3 python3-pip python3-venv && \ + apt install -y g++ && \ + apt install -y zlib1g-dev && \ + apt install -y wget + +RUN wget https://github.com/weizhongli/cdhit/releases/download/V4.8.1/cd-hit-v4.8.1-2019-0228.tar.gz && \ + tar xzf cd-hit-v4.8.1-2019-0228.tar.gz && \ + rm -rf cd-hit-v4.8.1-2019-0228.tar.gz && \ + cd cd-hit-v4.8.1-2019-0228 && \ + make && \ + make install && \ + cd .. && \ + rm -rf cd-hit-v4.8.1-2019-0228 + +RUN wget https://mafft.cbrc.jp/alignment/software/mafft_7.450-1_amd64.deb && \ + dpkg -i mafft_7.450-1_amd64.deb && \ + rm -rf mafft_7.450-1_amd64.deb + +RUN mkdir /app +WORKDIR /app + +COPY requirements.txt /app +RUN python3 -m venv .venv && \ + . .venv/bin/activate && \ + python -m pip install -U pip && \ + pip install -U setuptools && \ + pip install -U wheel && \ + pip install -r requirements.txt + +COPY . /app +RUN cd UnitePSSMs && \ + g++ *.cpp -std=c++11 -O3 -o UnitePSSMs && \ + cd ../PSSM_score_Peptide && \ + g++ *.cpp -std=c++11 -O3 -o PSSM_score_Peptide && \ + cd ../hits_cpp && \ + g++ *.cpp -std=c++11 -O3 -o hits && \ + cd ../tfidf && \ + g++ *.cpp -std=c++11 -O3 -o tfidf + +ENV APP_FILE IgOmeProfiling_pipeline.py +RUN chmod +x entrypoint.sh +ENTRYPOINT ["./entrypoint.sh"] +CMD ["-h"] \ No newline at end of file diff --git a/DockerfileWorker b/DockerfileWorker new file mode 100644 index 0000000..1ab87df --- /dev/null +++ b/DockerfileWorker @@ -0,0 +1,3 @@ +FROM webiks/igome-profile:latest +RUN chmod +x worker_entrypoint.sh +ENTRYPOINT ["./worker_entrypoint.sh"] \ No newline at end of file diff --git a/IgOmeProfiling_pipeline.py b/IgOmeProfiling_pipeline.py index 4104ae8..992444b 100644 --- a/IgOmeProfiling_pipeline.py +++ b/IgOmeProfiling_pipeline.py @@ -1,171 +1,387 @@ -import datetime -import os -import sys -if os.path.exists('/groups/pupko/orenavr2/'): - src_dir = '/groups/pupko/orenavr2/igomeProfilingPipeline/src' -else: - src_dir = '/Users/Oren/Dropbox/Projects/gershoni/src' -sys.path.insert(0, src_dir) - -from auxiliaries.pipeline_auxiliaries import * - -def run_pipeline(fastq_path, barcode2samplename_path, samplename2biologicalcondition_path, analysis_dir, logs_dir, - left_construct, right_construct, max_mismatches_allowed, min_sequencing_quality, gz, - max_msas_per_sample, max_msas_per_bc, - max_number_of_cluster_members_per_sample, max_number_of_cluster_members_per_bc, - allowed_gap_frequency, number_of_random_pssms, - run_summary_path, error_path, queue, verbose, argv): - - os.makedirs(os.path.split(run_summary_path)[0], exist_ok=True) - - f_run_summary_path = open(run_summary_path, 'w') - f_run_summary_path.write(' '.join(argv) + '\n\n') - f_run_summary_path.flush() - - start_time = datetime.datetime.now() - - exp_name = analysis_dir.rstrip('/').split('/')[-2] - - # output folders of the different modules - first_phase_output_path = os.path.join(analysis_dir, 'reads_filtration') - second_phase_output_path = os.path.join(analysis_dir, 'motif_inference') - third_phase_output_path = os.path.join(analysis_dir, 'model_fitting') - - first_phase_done_path = f'{logs_dir}/reads_filtration_done.txt' - if not os.path.exists(first_phase_done_path): - os.makedirs(first_phase_output_path, exist_ok=True) - first_phase_logs_path = os.path.join(logs_dir, 'reads_filtration') - os.makedirs(first_phase_logs_path, exist_ok=True) - - module_parameters = [fastq_path, first_phase_output_path, first_phase_logs_path, - barcode2samplename_path, left_construct, right_construct, - max_mismatches_allowed, min_sequencing_quality, first_phase_done_path, - '--gz' if gz else '', f'--error_path {error_path}', '-v' if verbose else ''] - cmd = submit_pipeline_step(f'{src_dir}/reads_filtration/module_wraper.py', - [module_parameters], - logs_dir, f'{exp_name}_reads_filtration', - queue, verbose) - - wait_for_results('reads_filtration', logs_dir, num_of_expected_results=1, example_cmd=cmd, - error_file_path=error_path, suffix='reads_filtration_done.txt') - else: - logger.info(f'{datetime.datetime.now()}: skipping reads filtration. Done file exists at:\n{first_phase_done_path}') - - second_phase_done_path = f'{logs_dir}/motif_inference_done.txt' - if not os.path.exists(second_phase_done_path): - os.makedirs(second_phase_output_path, exist_ok=True) - second_phase_logs_path = os.path.join(logs_dir, 'motif_inference') - os.makedirs(second_phase_logs_path, exist_ok=True) - - module_parameters = [first_phase_output_path, second_phase_output_path, second_phase_logs_path, - samplename2biologicalcondition_path, max_msas_per_sample, max_msas_per_bc, - max_number_of_cluster_members_per_sample, max_number_of_cluster_members_per_bc, - allowed_gap_frequency, second_phase_done_path, - f'--error_path {error_path}', '-v' if verbose else '', f'-q {queue}'] - cmd = submit_pipeline_step(f'{src_dir}/motif_inference/module_wraper.py', - [module_parameters], - logs_dir, f'{exp_name}_motif_inference', - queue, verbose) - - wait_for_results('motif_inference', logs_dir, num_of_expected_results=1, example_cmd=cmd, - error_file_path=error_path, suffix='motif_inference_done.txt') - else: - logger.info(f'{datetime.datetime.now()}: skipping motif inference. Done file exists at:\n{second_phase_done_path}') - - third_phase_done_path = f'{logs_dir}/model_fitting_done.txt' - if not os.path.exists(third_phase_done_path): - os.makedirs(third_phase_output_path, exist_ok=True) - third_phase_logs_path = os.path.join(logs_dir, 'model_fitting') - os.makedirs(third_phase_logs_path, exist_ok=True) - - module_parameters = [first_phase_output_path, second_phase_output_path, third_phase_output_path, - third_phase_logs_path, samplename2biologicalcondition_path, number_of_random_pssms, - third_phase_done_path, f'--error_path {error_path}', '-v' if verbose else '', - f'-q {queue}'] - cmd = submit_pipeline_step(f'{src_dir}/model_fitting/module_wraper.py', - [module_parameters], - logs_dir, f'{exp_name}_model_fitting', - queue, verbose) - - wait_for_results('model_fitting', logs_dir, num_of_expected_results=1, example_cmd=cmd, - error_file_path=error_path, suffix='model_fitting_done.txt') - else: - logger.info(f'{datetime.datetime.now()}: skipping model fitting. Done file exists {third_phase_done_path}') - - end_time = datetime.datetime.now() - f_run_summary_path.write(f'Total running time: {str(end_time-start_time)[:-3]}') - f_run_summary_path.close() - - logger.info(f'Started running at {start_time}') - logger.info(f'Done running at {end_time}') - logger.info(f'Total running time: {str(end_time-start_time)[:-3]}') - logger.info('Bye!') - - - -if __name__ == '__main__': - print(f'Starting {sys.argv[0]}. Executed command is:\n{" ".join(sys.argv)}', flush=True) - - import argparse - parser = argparse.ArgumentParser() - - parser.add_argument('fastq_path', type=str, help='A fastq file to parse') - parser.add_argument('barcode2samplename_path', type=str, help='A path to the barcode to sample name file') - parser.add_argument('samplename2biologicalcondition_path', type=str, help='A path to the sample name to biological condition file') - parser.add_argument('analysis_dir', type=str, help='analysis folder') - parser.add_argument('logs_dir', type=str, help='logs folder') - - # optional parameters for the filtration step - parser.add_argument('--left_construct', default='CAACGTGGC', help='The (constant) sequence from the LEFT of the random sequence') # in exp12: "CAACGTGGC" - parser.add_argument('--right_construct', default='GCCT', help='The (constant) sequence from the RIGHT of the random sequence') # in exp12: "GCCT" - parser.add_argument('--max_mismatches_allowed', type=int, default=1, - help='number of mismatches allowed together in both constant sequences') - parser.add_argument('--min_sequencing_quality', type=int, default=38, - help='Minimum average sequencing threshold allowed after filtration' - 'for more details, see: https://en.wikipedia.org/wiki/Phred_quality_score') - parser.add_argument('--gz', action='store_true', help='gzip fastq, filtration_log, fna, and faa files') - - # optional parameters for the motif inference - parser.add_argument('--max_msas_per_sample', default=100, type=int, - help='For each sample, align only the biggest $max_msas_per_sample') - parser.add_argument('--max_msas_per_bc', default=400, type=int, - help='For each biological condition, align only the biggest $max_msas_per_bc') - parser.add_argument('--max_number_of_cluster_members_per_sample', default=1000, type=int, - help='How many members (at most) should be taken to each cluster') - parser.add_argument('--max_number_of_cluster_members_per_bc', default=100, type=int, - help='How many members (at most) should be taken to each cluster') - parser.add_argument('--allowed_gap_frequency', default=0.9, - help='Maximal gap frequency allowed in msa (higher frequency columns are removed)', - type=lambda x: float(x) if 0 < float(x) < 1 - else parser.error(f'The threshold of the maximal gap frequency allowed per column should be between 0 to 1')) - - # optional parameters for the modelling step - parser.add_argument('--number_of_random_pssms', default=100, type=int, help='Number of pssm permutations') - - # general optional parameters - parser.add_argument('--run_summary_path', type=str, - help='a file in which the running configuration and timing will be written to') - parser.add_argument('--error_path', type=str, help='a file in which errors will be written to') - parser.add_argument('-q', '--queue', default='pupkoweb', type=str, help='a queue to which the jobs will be submitted') - parser.add_argument('-v', '--verbose', action='store_true', help='Increase output verbosity') - - args = parser.parse_args() - - import logging - if args.verbose: - logging.basicConfig(level=logging.DEBUG) - else: - logging.basicConfig(level=logging.INFO) - logger = logging.getLogger('main') - - run_summary_path = args.error_path if args.error_path else os.path.join(args.analysis_dir, 'run_summary_path.txt') - error_path = args.error_path if args.error_path else os.path.join(args.logs_dir, 'error.txt') - - run_pipeline(args.fastq_path, args.barcode2samplename_path, args.samplename2biologicalcondition_path, - args.analysis_dir.rstrip('/'), args.logs_dir.rstrip('/'), - args.left_construct, args.right_construct, args.max_mismatches_allowed, args.min_sequencing_quality, True if args.gz else False, - args.max_msas_per_sample, args.max_msas_per_bc, - args.max_number_of_cluster_members_per_sample, args.max_number_of_cluster_members_per_bc, - args.allowed_gap_frequency, args.number_of_random_pssms, - run_summary_path, error_path, args.queue, True if args.verbose else False, sys.argv) - +import datetime +from genericpath import exists +import os +import sys +import json +if os.path.exists('/groups/pupko/orenavr2/'): + src_dir = '/groups/pupko/orenavr2/igomeProfilingPipeline/src' +elif os.path.exists('/Users/Oren/Dropbox/Projects/'): + src_dir = '/Users/Oren/Dropbox/Projects/gershoni/src' +else: + src_dir = '.' +sys.path.insert(0, src_dir) + +from auxiliaries.pipeline_auxiliaries import * +from auxiliaries.validation_files import is_input_files_valid +from auxiliaries.stop_machine_aws import stop_machines + +schema = { + 'reads': schema_reads, + 'inference': schema_inference, + 'model': schema_cross_exp +} + +def validate_input_file(config, phase, logger): + f = open(config) + config_dict = json.load(f) + samplename2biologicalcondition_paths = [] + barcode2samplename_paths = [] + is_valid = is_valid_json_structure(config, config_dict, schema[phase], logger) + if not is_valid: + logger.info(f'Json file {config} is not valid') + return False + runs = config_dict['runs'] + for run in runs: + if phase == 'reads': + barcode2samplename_paths.append(runs[run]['barcode2sample']) + if phase == 'inference': + samplename2biologicalcondition_paths.append(runs[run]['sample2bc']) + if phase == 'model': + samplename2biologicalcondition_paths.append(runs[run]['sample2bc'].values()) + for path in barcode2samplename_paths: + is_valid = is_input_files_valid(samplename2biologicalcondition_path='', barcode2samplename_path=path, logger=logger) + if not is_valid: + return False + samplename2biologicalcondition_paths = list(set(samplename2biologicalcondition_paths)) + for path in samplename2biologicalcondition_paths: + is_valid = is_input_files_valid(samplename2biologicalcondition_path=path, barcode2samplename_path='', logger=logger) + if not is_valid: + return False + return True + + +def is_all_files_valid(multi_exp_config_reads, multi_exp_config_inference, cross_experiments_config, samplename2biologicalcondition_path, barcode2samplename_path, logger): + is_files_valid = True + is_files_valid_reads = True + is_files_valid_inference = True + is_files_valid_model = True + + if os.path.exists(samplename2biologicalcondition_path) and os.path.exists(barcode2samplename_path): + is_files_valid = is_input_files_valid(samplename2biologicalcondition_path=samplename2biologicalcondition_path, barcode2samplename_path=barcode2samplename_path, logger=logger) + else: + if multi_exp_config_reads: + is_files_valid_reads = validate_input_file(multi_exp_config_reads, 'reads', logger) + if multi_exp_config_inference: + is_files_valid_inference = validate_input_file(multi_exp_config_inference, 'inference', logger) + if cross_experiments_config: + is_files_valid_model = validate_input_file(cross_experiments_config, 'model', logger) + + if not is_files_valid or not is_files_valid_reads or not is_files_valid_inference or not is_files_valid_model: + return False + return True + + +def run_pipeline(fastq_path, barcode2samplename_path, samplename2biologicalcondition_path, analysis_dir, logs_dir, + left_construct, right_construct, max_mismatches_allowed, min_sequencing_quality, minimal_length_required, + maximum_length_required, gz, no_calculate_rpm, multi_exp_config_reads, name_summary_file_reads, + max_msas_per_sample, max_msas_per_bc, max_number_of_cluster_members_per_sample, max_number_of_cluster_members_per_bc, + allowed_gap_frequency, threshold, word_length, discard, cluster_algorithm_mode, concurrent_cutoffs, meme_split_size, use_mapitope, aln_cutoff, + pcc_cutoff, sort_cluster_to_combine_only_by_cluster_size, min_number_samples_build_cluster_per_BC, + skip_sample_merge_meme, minimal_number_of_columns_required_create_meme, prefix_length_in_clstr, + multi_exp_config_inference, cutoff_random_peptitdes_percentile, min_library_length_cutoff, max_library_length_cutoff, + stop_before_random_forest, is_run_random_forest_per_bc_sequentially, number_of_random_pssms, number_parallel_random_forest, min_value_error_random_forest, + rank_method, tfidf_method, tfidf_factor, shuffles, shuffles_percent, shuffles_digits, + num_of_random_configurations_to_sample, cv_num_of_splits, seed_random_forest, random_forest_seed_configurations, + stop_machines_flag, type_machines_to_stop, name_machines_to_stop, cross_experiments_config, no_rpm_factor, + no_use_rpm_faa_scanning, no_output_sequences_scanning, filter_positive_motifs, invalid_mix_positive_motifs, threshold_mean_positive_motifs, + threshold_std_positive_motifs, threshold_median_positive_motifs, min_max_difference_positive_motifs, normalize_factor_positive_motifs, + normalize_method_hits_positive_motifs, normalize_section_positive_motifs, fixed_min_positive_motifs, fixed_max_positive_motifs, + run_summary_path, error_path, queue, verbose, argv): + + files_are_valid = True + if multi_exp_config_reads or multi_exp_config_inference or cross_experiments_config: + files_are_valid = is_all_files_valid(multi_exp_config_reads, multi_exp_config_inference, cross_experiments_config, + samplename2biologicalcondition_path, barcode2samplename_path, logger) + else: + # check the validation of files barcode2samplename_path and samplename2biologicalcondition_path + files_are_valid = is_input_files_valid(samplename2biologicalcondition_path=samplename2biologicalcondition_path, barcode2samplename_path=barcode2samplename_path, logger=logger) + if not files_are_valid: + return + + os.makedirs(os.path.split(run_summary_path)[0], exist_ok=True) + + f_run_summary_path = open(run_summary_path, 'w') + f_run_summary_path.write(' '.join(argv) + '\n\n') + f_run_summary_path.flush() + + start_time = datetime.datetime.now() + + exp_name = analysis_dir.rstrip('/').split('/')[-2] + + # output folders of the different modules + first_phase_output_path = os.path.join(analysis_dir, 'reads_filtration') + second_phase_output_path = os.path.join(analysis_dir, 'motif_inference') + third_phase_output_path = os.path.join(analysis_dir, 'model_fitting') + + first_phase_done_path = f'{logs_dir}/reads_filtration_done.txt' + if not os.path.exists(first_phase_done_path): + os.makedirs(first_phase_output_path, exist_ok=True) + first_phase_logs_path = os.path.join(logs_dir, 'reads_filtration') + os.makedirs(first_phase_logs_path, exist_ok=True) + + module_parameters = [fastq_path, first_phase_output_path, first_phase_logs_path, + barcode2samplename_path, left_construct, right_construct, + max_mismatches_allowed, min_sequencing_quality, first_phase_done_path, minimal_length_required, + f'--maximum_length_required {maximum_length_required}', + '--check_files_valid' if not files_are_valid else '', + f'--multi_exp_config_reads {multi_exp_config_reads}' if multi_exp_config_reads else '', + '--no_calculate_rpm' if no_calculate_rpm else '', + f'--name_summary_file_reads {name_summary_file_reads}', '--gz' if gz else '', f'--error_path {error_path}', + '-v' if verbose else '', '-m' if use_mapitope else ''] + + cmd = submit_pipeline_step(f'{src_dir}/reads_filtration/module_wraper.py',[module_parameters], + logs_dir, f'{exp_name}_reads_filtration', queue, verbose) + + wait_for_results('reads_filtration', logs_dir, num_of_expected_results=1, example_cmd=cmd, + error_file_path=error_path, suffix='reads_filtration_done.txt') + else: + logger.info(f'{datetime.datetime.now()}: skipping reads filtration. Done file exists at:\n{first_phase_done_path}') + + second_phase_done_path = f'{logs_dir}/motif_inference_done.txt' + if not os.path.exists(second_phase_done_path): + os.makedirs(second_phase_output_path, exist_ok=True) + second_phase_logs_path = os.path.join(logs_dir, 'motif_inference') + os.makedirs(second_phase_logs_path, exist_ok=True) + + module_parameters = [first_phase_output_path, second_phase_output_path, second_phase_logs_path, + samplename2biologicalcondition_path, max_msas_per_sample, max_msas_per_bc, + max_number_of_cluster_members_per_sample, max_number_of_cluster_members_per_bc, + allowed_gap_frequency, second_phase_done_path, '--check_files_valid' if not files_are_valid else '', + f'--minimal_number_of_columns_required_create_meme {minimal_number_of_columns_required_create_meme}', + f'--prefix_length_in_clstr {prefix_length_in_clstr}', f'--aln_cutoff {aln_cutoff}', f'--pcc_cutoff {pcc_cutoff}', + '--sort_cluster_to_combine_only_by_cluster_size' if sort_cluster_to_combine_only_by_cluster_size else '', + f'--min_number_samples_build_cluster_per_BC {min_number_samples_build_cluster_per_BC}', + f'--threshold {threshold}', f'--word_length {word_length}', f'--discard {discard}', f'--cluster_algorithm_mode {cluster_algorithm_mode}', + f'--multi_exp_config_inference {multi_exp_config_inference}' if multi_exp_config_inference else '', + f'--meme_split_size {meme_split_size}', f'--skip_sample_merge_meme {skip_sample_merge_meme}', + f'--cutoff_random_peptitdes_percentile {cutoff_random_peptitdes_percentile}', + f'--min_library_length_cutoff {min_library_length_cutoff}', f'--max_library_length_cutoff {max_library_length_cutoff}', + f'--error_path {error_path}', '-v' if verbose else '', f'-q {queue}','-m' if use_mapitope else ''] + if concurrent_cutoffs: + module_parameters.append('--concurrent_cutoffs') + cmd = submit_pipeline_step(f'{src_dir}/motif_inference/module_wraper.py', + [module_parameters], + logs_dir, f'{exp_name}_motif_inference', + queue, verbose) + + wait_for_results('motif_inference', logs_dir, num_of_expected_results=1, example_cmd=cmd, + error_file_path=error_path, suffix='motif_inference_done.txt') + else: + logger.info(f'{datetime.datetime.now()}: skipping motif inference. Done file exists at:\n{second_phase_done_path}') + + third_phase_done_path = f'{logs_dir}/model_fitting_done.txt' + if not os.path.exists(third_phase_done_path): + os.makedirs(third_phase_output_path, exist_ok=True) + third_phase_logs_path = os.path.join(logs_dir, 'model_fitting') + os.makedirs(third_phase_logs_path, exist_ok=True) + + module_parameters = [first_phase_output_path, second_phase_output_path, third_phase_output_path, + third_phase_logs_path, samplename2biologicalcondition_path, number_of_random_pssms, third_phase_done_path, + '--stop_before_random_forest' if stop_before_random_forest else '', f'--cross_experiments_config {cross_experiments_config}' if cross_experiments_config else '', + f'--num_of_random_configurations_to_sample {num_of_random_configurations_to_sample}', '--check_files_valid' if not files_are_valid else '', + f'--number_parallel_random_forest {number_parallel_random_forest}', f'--min_value_error_random_forest {min_value_error_random_forest}', + f'--shuffles_percent {shuffles_percent}', f'--shuffles_digits {shuffles_digits}', + f'--cv_num_of_splits {cv_num_of_splits}', f'--seed_random_forest {seed_random_forest}', + f'--random_forest_seed_configurations {random_forest_seed_configurations}', f'--rank_method {rank_method}', + '--is_run_random_forest_per_bc_sequentially' if is_run_random_forest_per_bc_sequentially else '', + '--no_rpm_factor' if no_rpm_factor else '', + '--no_use_rpm_faa_scanning' if no_use_rpm_faa_scanning else '', + f'--no_output_sequences_scanning' if no_output_sequences_scanning else '', + '--filter_positive_motifs' if filter_positive_motifs else '', + '' if invalid_mix_positive_motifs is None else f'--invalid_mix {invalid_mix_positive_motifs}', + '' if threshold_mean_positive_motifs is None else f'--threshold_mean {threshold_mean_positive_motifs}', + '' if threshold_std_positive_motifs is None else f'--threshold_std {threshold_std_positive_motifs}', + '' if threshold_median_positive_motifs is None else f'--threshold_median {threshold_median_positive_motifs}', + f'--min_max_difference_positive_motifs' if min_max_difference_positive_motifs else '', + f'--normalize_factor_positive_motifs {normalize_factor_positive_motifs}', + f'--normalize_method_hits_positive_motifs {normalize_method_hits_positive_motifs}', + f'--normalize_section_positive_motifs {normalize_section_positive_motifs}', + '' if fixed_min_positive_motifs is None else f'--fixed_min_positive_motifs {fixed_min_positive_motifs}', + '' if fixed_max_positive_motifs is None else f'--fixed_max_positive_motifs {fixed_max_positive_motifs}', + f'--error_path {error_path}', '-v' if verbose else '', f'-q {queue}','-m' if use_mapitope else ''] + if rank_method == 'tfidf': + if tfidf_method: + module_parameters += ['--tfidf_method', tfidf_method] + if tfidf_factor: + module_parameters += ['--tfidf_factor', str(tfidf_factor)] + elif rank_method == 'shuffles': + if shuffles: + module_parameters += ['--shuffles', shuffles] + cmd = submit_pipeline_step(f'{src_dir}/model_fitting/module_wraper.py', + [module_parameters], + logs_dir, f'{exp_name}_model_fitting', + queue, verbose) + + wait_for_results('model_fitting', logs_dir, num_of_expected_results=1, example_cmd=cmd, + error_file_path=error_path, suffix='model_fitting_done.txt') + else: + logger.info(f'{datetime.datetime.now()}: skipping model fitting. Done file exists {third_phase_done_path}') + + if stop_machines_flag: + stop_machines(type_machines_to_stop, name_machines_to_stop, logger) + + end_time = datetime.datetime.now() + f_run_summary_path.write(f'Total running time: {str(end_time-start_time)[:-3]}') + f_run_summary_path.close() + + logger.info(f'Started running at {start_time}') + logger.info(f'Done running at {end_time}') + logger.info(f'Total running time: {str(end_time-start_time)[:-3]}') + logger.info('Bye!') + +if __name__ == '__main__': + print(f'Starting {sys.argv[0]}. Executed command is:\n{" ".join(sys.argv)}', flush=True) + + import argparse + parser = argparse.ArgumentParser() + + parser.add_argument('fastq_path', type=str, help='A fastq file to parse') + parser.add_argument('barcode2samplename_path', type=str, help='A path to the barcode to sample name file') + parser.add_argument('samplename2biologicalcondition_path', type=str, help='A path to the sample name to biological condition file') + parser.add_argument('analysis_dir', type=str, help='analysis folder') + parser.add_argument('logs_dir', type=str, help='logs folder') + + # optional parameters for the filtration step + parser.add_argument('--left_construct', default='CAACGTGGC', help='The (constant) sequence from the LEFT of the random sequence') # in exp12: "CAACGTGGC" + parser.add_argument('--right_construct', default='GCCT', help='The (constant) sequence from the RIGHT of the random sequence') # in exp12: "GCCT" + parser.add_argument('--max_mismatches_allowed', type=int, default=1, + help='number of mismatches allowed together in both constant sequences') + parser.add_argument('--min_sequencing_quality', type=int, default=38, + help='Minimum average sequencing threshold allowed after filtration' + 'for more details, see: https://en.wikipedia.org/wiki/Phred_quality_score') + parser.add_argument('--minimal_length_required', default=3, type=int, help='Shorter peptides will be discarded') + parser.add_argument('--maximum_length_required', default=14, type=int, help='Longer peptides will be discarded') + parser.add_argument('--gz', action='store_true', help='gzip fastq, filtration_log, fna, and faa files') + parser.add_argument('--no_calculate_rpm', action='store_true', help='Disable normalize counts to "reads per million" (sequence proportion x 1,000,000)') + parser.add_argument('--multi_exp_config_reads', type=str, help='Configuration file for reads phase to run multi expirements') + parser.add_argument('--name_summary_file_reads', default='summary_log_reads.csv', type=str, help='A name for summary file summary all reads.') + + # optional parameters for the motif inference + parser.add_argument('--max_msas_per_sample', default=100, type=int, + help='For each sample, align only the biggest $max_msas_per_sample') + parser.add_argument('--max_msas_per_bc', default=400, type=int, + help='For each biological condition, align only the biggest $max_msas_per_bc') + parser.add_argument('--max_number_of_cluster_members_per_sample', default=1000, type=int, + help='How many members (at most) should be taken to each cluster') + parser.add_argument('--max_number_of_cluster_members_per_bc', default=100, type=int, + help='How many members (at most) should be taken to each cluster') + parser.add_argument('--allowed_gap_frequency', default=0.9, + help='Maximal gap frequency allowed in msa (higher frequency columns are removed)', + type=lambda x: float(x) if 0 < float(x) < 1 + else parser.error(f'The threshold of the maximal gap frequency allowed per column should be between 0 to 1')) + parser.add_argument('--threshold', default='0.6', help='Minimal sequence similarity threshold required', + type=lambda x: float(x) if 0.4 <= float(x) <= 1 + else parser.error(f'CD-hit allows thresholds between 0.4 to 1')) + parser.add_argument('--word_length', default='4', choices=['2', '3', '4', '5'], + help='A heuristic of CD-hit. Choose of word size:\n5 for similarity thresholds 0.7 ~ 1.0\n4 for similarity thresholds 0.6 ~ 0.7\n3 for similarity thresholds 0.5 ~ 0.6\n2 for similarity thresholds 0.4 ~ 0.5') + parser.add_argument('--discard', default='4', help='Include only sequences longer than <$discard> for the analysis. (CD-hit uses only sequences that are longer than 10 amino acids. When the analysis includes shorter sequences, this threshold should be lowered. Thus, it is set here to 1 by default.)') + parser.add_argument('--cluster_algorithm_mode', default='1', type=str, help='0 - clustered to the first cluster that meet the threshold (fast). 1 - clustered to the most similar cluster (slow)') + parser.add_argument('--concurrent_cutoffs', action='store_true', + help='Use new method which splits meme before cutoffs and runs cutoffs concurrently') + parser.add_argument('--meme_split_size', type=int, default=1, # TODO default of 1, 5 or 10? + help='Split size, how many meme per files for calculations') + parser.add_argument('-m', '--mapitope', action='store_true', help='use mapitope encoding') + parser.add_argument('--aln_cutoff', default='24', help='The cutoff for pairwise alignment score to unite motifs of BC') + parser.add_argument('--pcc_cutoff', default='0.7', help='Minimal PCC R to unite motifs of BC') + parser.add_argument('--sort_cluster_to_combine_only_by_cluster_size', action='store_true', help='Sort the clusters only by the cluster size') + parser.add_argument('--min_number_samples_build_cluster_per_BC', type=str, default=1, help='Keep only clusters that build from X minimum number of samples') + parser.add_argument('--skip_sample_merge_meme', default='a_weird_str_that_shouldnt_be_a_sample_name_by_any_chance', + help='A sample name that should be skipped in merge meme files, e.g., for testing purposes. More than one sample ' + 'name should be separated by commas but no spaces. ' + 'For example: 17b_05,17b_05_test,another_one') + parser.add_argument('--minimal_number_of_columns_required_create_meme', default=1, type=int, + help='MSAs with less than the number of required columns will be skipped') + parser.add_argument('--prefix_length_in_clstr', default=20, type=int, + help='How long should be the prefix that is taken from the clstr file (cd-hit max prefix is 20)') + parser.add_argument('--multi_exp_config_inference', type=str, help='Configuration file for inference motifs phase to run multi expirements') + parser.add_argument('--cutoff_random_peptitdes_percentile', type=float, default=0.05, help='Calculate cutoff (hit threshold) from random peptides\' top percentile score') + parser.add_argument('--min_library_length_cutoff', type=int, default=5, help='Minimal value of libraries to generate random peptitdes') + parser.add_argument('--max_library_length_cutoff', type=int, default=14, help='Maximum value of libraries to generate random peptitdes') + + # optional parameters for the modelling step + parser.add_argument('--cross_experiments_config', type=str, help='Configuration file to run cross expiremets at model fitting phase') + parser.add_argument('--stop_before_random_forest', action='store_true', help='A boolean flag for mark if we need to run the random forest') + parser.add_argument('--number_of_random_pssms', default=100, type=int, help='Number of pssm permutations') + parser.add_argument('--number_parallel_random_forest', default=20, type=int, help='How many random forest configurations to run in parallel') + parser.add_argument('--min_value_error_random_forest', default=0, type=float, help='A random forest model error value for convergence allowing to stop early') + parser.add_argument('--rank_method', choices=['pval', 'tfidf', 'shuffles'], default='shuffles', help='Motifs ranking method') + parser.add_argument('--tfidf_method', choices=['boolean', 'terms', 'log', 'augmented'], default='boolean', help='TF-IDF method') + parser.add_argument('--tfidf_factor', type=float, default=0.5, help='TF-IDF augmented method factor (0-1)') + parser.add_argument('--shuffles', default=10, type=int, help='Number of controlled shuffles permutations') + parser.add_argument('--shuffles_percent', default=0.2, type=float, help='Percent from shuffle with greatest number of hits (0-1)') + parser.add_argument('--shuffles_digits', default=2, type=int, help='Number of digits after the point to print in scanning files.') + parser.add_argument('--num_of_random_configurations_to_sample', default=100, type=int, help='How many random configurations of hyperparameters should be sampled?') + parser.add_argument('--cv_num_of_splits', type=int, default=2, help='How folds should be in the cross validation process? (use 0 for leave one out)') + parser.add_argument('--seed_random_forest', type=int, default=42, help='Seed number for reconstructing experiments') + parser.add_argument('--random_forest_seed_configurations', default=123 , type=int, help='Random seed value for generating random forest configurations') + parser.add_argument('--stop_machines', action='store_true', help='Turn off the machines in AWS at the end of the running') + parser.add_argument('--type_machines_to_stop', default='', type=str, help='Type of machines to stop, separated by comma. Empty value means all machines. Example: t2.2xlarge,m5a.24xlarge ') + parser.add_argument('--name_machines_to_stop', default='', type=str, help='Names (patterns) of machines to stop, separated by comma. Empty value means all machines. Example: worker*') + parser.add_argument('--is_run_random_forest_per_bc_sequentially', action='store_true', help='Set the flag to true when number of cores is less than number of BC X 2 (hit and value), otherwise it will run all the BC parallel (on the same time)') + parser.add_argument('--no_rpm_factor', action='store_true', help='Disable multiplication hits by factor rpm for normalization') + parser.add_argument('--no_use_rpm_faa_scanning', action='store_true', help='Disable performance of scanning script with unique rpm faa file') + parser.add_argument('--no_output_sequences_scanning', action='store_true', help='Disable storing the output sequences that had hits') + parser.add_argument('--filter_positive_motifs', action='store_true', help='Filter only positive motifs. This is done after motifs generation and before ranking') + parser.add_argument('--invalid_mix_positive_motifs',type=str, default=None, help='Sample name considered negative. e.g. "native') + parser.add_argument('--threshold_mean_positive_motifs', default=None, + type=lambda x: float(x) if 0 < float(x) < 1 + else parser.error(f'The threshold of the mean diffrence should be between 0 to 1'), + help='Positive motif threshold of the difference between the mean of the BC and mean of others') + parser.add_argument('--threshold_std_positive_motifs', default=None, + type=lambda x: float(x) if 0 < float(x) < 1 + else parser.error(f'The threshold of the std diffrence should be between 0 to 1'), + help='Positive motif threshold of the difference between the std of the BC and std of others') + parser.add_argument('--threshold_median_positive_motifs', default=None, + type=lambda x: float(x) if 0 < float(x) < 1 + else parser.error(f'The threshold of the median diffrence should be between 0 to 1'), + help='Positive motif threshold of the difference between the median of the BC and median of others') + parser.add_argument('--min_max_difference_positive_motifs', action='store_true', help='Positive motif if the minmal value of bc is bigger than the maximal value of other') + parser.add_argument('--normalize_factor_positive_motifs', choices=['linear', 'log'], default='linear', help='type of factor to normalize the data. \ + If nonlinear data (values are not in the same distance) use log ,otherwise use linear') + parser.add_argument('--normalize_method_hits_positive_motifs', choices=['min_max', 'max', 'fixed_min_max'], default='min_max', + help='Method affects which values the normalization will change the data. e.g. min max bring all values into the range [0,1]') + parser.add_argument('--normalize_section_positive_motifs', choices=['per_motif','per_exp'], default='per_motif', help='Normalize the data by calculate the min and max per motif or over all the exp data') + parser.add_argument('--fixed_min_positive_motifs', type=int, default=None, help='In case of fixed_min_max for normalize_method_hits set the minimum value') + parser.add_argument('--fixed_max_positive_motifs', type=int, default=None, help='In case of fixed_min_max for normalize_method_hits set the maximum value') + + # general optional parameters + parser.add_argument('--run_summary_path', type=str, + help='a file in which the running configuration and timing will be written to') + parser.add_argument('--error_path', type=str, help='a file in which errors will be written to') + parser.add_argument('-q', '--queue', default='pupkoweb', type=str, help='a queue to which the jobs will be submitted') + parser.add_argument('-v', '--verbose', action='store_true', help='Increase output verbosity') + + args = parser.parse_args() + + import logging + if args.verbose: + logging.basicConfig(level=logging.DEBUG) + else: + logging.basicConfig(level=logging.INFO) + logger = logging.getLogger('main') + + run_summary_path = args.error_path if args.error_path else os.path.join(args.analysis_dir, 'run_summary_path.txt') + error_path = args.error_path if args.error_path else os.path.join(args.logs_dir, 'error.txt') + + concurrent_cutoffs = True if args.concurrent_cutoffs else False + + run_pipeline(args.fastq_path, args.barcode2samplename_path, args.samplename2biologicalcondition_path, + args.analysis_dir.rstrip('/'), args.logs_dir.rstrip('/'), + args.left_construct, args.right_construct, args.max_mismatches_allowed, args.min_sequencing_quality, args.minimal_length_required, + args.maximum_length_required, args.gz, args.no_calculate_rpm, args.multi_exp_config_reads, args.name_summary_file_reads, + args.max_msas_per_sample, args.max_msas_per_bc, args.max_number_of_cluster_members_per_sample, args.max_number_of_cluster_members_per_bc, + args.allowed_gap_frequency, args.threshold, args.word_length, args.discard, args.cluster_algorithm_mode, concurrent_cutoffs, args.meme_split_size, + args.mapitope, args.aln_cutoff, args.pcc_cutoff, args.sort_cluster_to_combine_only_by_cluster_size, args.min_number_samples_build_cluster_per_BC, + args.skip_sample_merge_meme, args.minimal_number_of_columns_required_create_meme, + args.prefix_length_in_clstr, args.multi_exp_config_inference, args.cutoff_random_peptitdes_percentile, + args.min_library_length_cutoff, args.max_library_length_cutoff, + args.stop_before_random_forest, args.is_run_random_forest_per_bc_sequentially, args.number_of_random_pssms, + args.number_parallel_random_forest, args.min_value_error_random_forest, + args.rank_method, args.tfidf_method, args.tfidf_factor, args.shuffles, args.shuffles_percent, args.shuffles_digits, + args.num_of_random_configurations_to_sample, args.cv_num_of_splits, args.seed_random_forest, args.random_forest_seed_configurations, + args.stop_machines, args.type_machines_to_stop, args.name_machines_to_stop, args.cross_experiments_config, + args.no_rpm_factor, args.no_use_rpm_faa_scanning, args.no_output_sequences_scanning, + args.filter_positive_motifs, args.invalid_mix_positive_motifs, args.threshold_mean_positive_motifs, args.threshold_std_positive_motifs, + args.threshold_median_positive_motifs, args.min_max_difference_positive_motifs, args.normalize_factor_positive_motifs, + args.normalize_method_hits_positive_motifs, args.normalize_section_positive_motifs, args.fixed_min_positive_motifs, args.fixed_max_positive_motifs, + run_summary_path, error_path, args.queue, args.verbose, sys.argv) diff --git a/PSSM_score_Peptide/PSSM.cpp b/PSSM_score_Peptide/PSSM.cpp index d7d3851..7d50007 100644 --- a/PSSM_score_Peptide/PSSM.cpp +++ b/PSSM_score_Peptide/PSSM.cpp @@ -2,6 +2,7 @@ #include "SEQ.h" #include +#include using namespace std; void PSSM::setMatrix(const vector & PSSMLines) @@ -179,8 +180,8 @@ double PSSM::computeScoreExacrPos(size_t posInPSSM, const size_t charInSeq) cons }// end of function */ -PSSM PSSM::randomize() { +PSSM PSSM::randomize(default_random_engine &gen) { PSSM res(*this); - random_shuffle(res.PSSMmatrix.begin(), res.PSSMmatrix.end()); + shuffle(res.PSSMmatrix.begin(), res.PSSMmatrix.end(), gen); return res; } \ No newline at end of file diff --git a/PSSM_score_Peptide/PSSM.h b/PSSM_score_Peptide/PSSM.h index 9ef3c67..06d71c6 100644 --- a/PSSM_score_Peptide/PSSM.h +++ b/PSSM_score_Peptide/PSSM.h @@ -9,6 +9,7 @@ #include // std::min_element, std::max_element #include #include // because of unix +#include using namespace std; #include "alphabet.h" @@ -43,7 +44,7 @@ class PSSM { return PSSMmatrix.size(); } - PSSM randomize(); + PSSM randomize(default_random_engine &gen); //int integerOfChar(const char s) const; //members: diff --git a/PSSM_score_Peptide/SEQ.cpp b/PSSM_score_Peptide/SEQ.cpp index 8d6dda3..157361b 100644 --- a/PSSM_score_Peptide/SEQ.cpp +++ b/PSSM_score_Peptide/SEQ.cpp @@ -53,11 +53,13 @@ string SEQ::getStringOfSeq(size_t pos) const { return res; } -SEQ::SEQ(string & SeqString, const string & SeqName, const double CopyNumber, alphabet& alph) : _Seq_Name(SeqName), _CopyNumber(CopyNumber), _alph(alph) { +SEQ::SEQ(string & SeqString, const string & SeqName, const double CopyNumber, alphabet& alph, bool isSetCopyNumber = true) : _Seq_Name(SeqName), _CopyNumber(CopyNumber), _alph(alph) { for (size_t k = 0; k < SeqString.length(); ++k) { char i = SeqString.at(k); _Seq.push_back(alph._alphabetMap[i]); } setSeq_Type(); - setCopyNmber(); + if (isSetCopyNumber) { + setCopyNmber(); + } }; diff --git a/PSSM_score_Peptide/SEQ.h b/PSSM_score_Peptide/SEQ.h index 2bdeaef..b72f97b 100644 --- a/PSSM_score_Peptide/SEQ.h +++ b/PSSM_score_Peptide/SEQ.h @@ -21,7 +21,7 @@ class SEQ { setSeq_Type(); setCopyNmber(); }; - SEQ(string & SeqString, const string & SeqName, const double CopyNumber, alphabet& alph); + SEQ(string & SeqString, const string & SeqName, const double CopyNumber, alphabet& alph, bool isSetCopyNumber); ~SEQ(){}; void setName(const string name ){ _Seq_Name = name;} diff --git a/PSSM_score_Peptide/computePSSM_cutoffs.cpp b/PSSM_score_Peptide/computePSSM_cutoffs.cpp index 80eb13f..8173804 100644 --- a/PSSM_score_Peptide/computePSSM_cutoffs.cpp +++ b/PSSM_score_Peptide/computePSSM_cutoffs.cpp @@ -9,7 +9,19 @@ void PSSM_scoresFromSeqVector(const PSSM& PSSM1, const vector> & computePSSM_cutoffs::computePSSM_cutoffs(vector & PSSM_array, size_t TotalNumberOfRandoSeq, alphabet& alph, - const string & CutofsPerPSSM_FileName) : _PSSM_array(PSSM_array), _totalNumberOfRandoSeq(TotalNumberOfRandoSeq), _alph(alph), _CutofsPerPSSM_FileName(CutofsPerPSSM_FileName) + const string & CutofsPerPSSM_FileName, + int totalMemes, + double PercentOfRandomHitsPerPSSM, + int minLibraryLength, + int maxLibraryLength) : + _PSSM_array(PSSM_array), + _totalNumberOfRandoSeq(TotalNumberOfRandoSeq), + _alph(alph), + _CutofsPerPSSM_FileName(CutofsPerPSSM_FileName), + _totalMemes(totalMemes), + _PercentOfRandomHitsPerPSSM(PercentOfRandomHitsPerPSSM), + _minLibraryLength(minLibraryLength), + _maxLibraryLength(maxLibraryLength) { generateRandomPeptides(); computecCutoffsBasedOnRandomPeptides(); @@ -23,8 +35,9 @@ string SizetToString(size_t sz) { void computePSSM_cutoffs::generateRandomPeptides() { size_t NumberOfRandoSeq = _totalNumberOfRandoSeq; + srand(931); // Set srand for generating random pepties // TODO set srand from input argument map::iterator it = _randomPeptideDataSet.begin(); // use iteration and insert to add values to map - for (size_t length = 5; length <= 12; length++) + for (size_t length = _minLibraryLength; length <= _maxLibraryLength; length++) { randomPeptides tmp(_alph._aaFreq, NumberOfRandoSeq, length); tmp.generateRandomSequences(); @@ -72,8 +85,7 @@ void computePSSM_cutoffs::computecCutoffsBasedOnRandomPeptides() { // determin cutoff for each seq type and print to file ofstream PSSM_Scores_Cutoff; PSSM_Scores_Cutoff.open(_CutofsPerPSSM_FileName); - - double PercentOfAcceptedPeptidesPerType = PercentOfRandomHitsPerPSSM / _PSSM_array.size(); // for each seq type the cutoff will be the percent of hits accepted for all PSSMs devided by the number of PSSMs + double PercentOfAcceptedPeptidesPerType = _PercentOfRandomHitsPerPSSM / _totalMemes; // for each seq type the cutoff will be the percent of hits accepted for all PSSMs devided by the number of PSSMs for (size_t i = 0; i<_PSSM_array.size(); ++i) { PSSM_Scores_Cutoff << "###\t" << _PSSM_array[i].PSSM_name << "\t"; diff --git a/PSSM_score_Peptide/computePSSM_cutoffs.h b/PSSM_score_Peptide/computePSSM_cutoffs.h index ad3948f..97a3fb0 100644 --- a/PSSM_score_Peptide/computePSSM_cutoffs.h +++ b/PSSM_score_Peptide/computePSSM_cutoffs.h @@ -14,10 +14,14 @@ using namespace std; class computePSSM_cutoffs{ public: - computePSSM_cutoffs( vector & PSSM_array, - size_t TotalNumberOfRandoSeq, - alphabet & alph, - const string & CutofsPerPSSM_FileName); + computePSSM_cutoffs(vector & PSSM_array, + size_t TotalNumberOfRandoSeq, + alphabet & alph, + const string & CutofsPerPSSM_FileName, + int totalMemes, + double PercentOfRandomHitsPerPSSM, + int minLibraryLength, + int maxLibraryLength); private: void generateRandomPeptides(); @@ -30,8 +34,12 @@ class computePSSM_cutoffs{ //vector const & _correspondingCharacters; map _randomPeptideDataSet; // map between seqType and object containing the random peptides dataset string const & _CutofsPerPSSM_FileName; - const double PercentOfRandomHitsPerPSSM = 0.05; alphabet& _alph; + int _totalMemes; + double _PercentOfRandomHitsPerPSSM; + int _minLibraryLength; + int _maxLibraryLength; + diff --git a/PSSM_score_Peptide/main.cpp b/PSSM_score_Peptide/main.cpp index 57d25de..0988718 100644 --- a/PSSM_score_Peptide/main.cpp +++ b/PSSM_score_Peptide/main.cpp @@ -6,6 +6,7 @@ #include #include // eli because of unix #include // because of unix +#include using namespace std; #include "PSSM.h" @@ -20,7 +21,7 @@ using namespace std; string PadSeq (string seq, size_t PaddingLength); void fromFileToVectorOfString(const string fileName,vector & allLines); void readFileToPSSM_array(const string fileName, vector & PSSM_array, map & PSSM_Name_To_ArrayIndex); -void readFileToSeq_array (const string fileName, alphabet& alph, vector &Seq_array); +void readFileToSeq_array (const string fileName, alphabet& alph, vector &Seq_array, bool useRpmFaaScanning, int& numberOfSeq, bool isSetCopyNumber=true); void get_top_hits (const vector & sorted_seq,double fraction, vector & top_hits); void fill_Seq_Hits_PSSM_map(const vector & seq_Hits_vector,map> &PSSM_Hits, string PSSM_name); vector get_sort_seq_vector_by_scores (vector & seq_vector, vector & scores_vector); @@ -63,12 +64,12 @@ int main(int argc, char *argv[]) { // The first is -pssm . // The second is- pssm_cutoffs size_t get_running_mode(int argc, char *argv[]){ - int max_required_params = 5; + int max_required_params = 8; int min_required_params = 2; if ((argc > (max_required_params * 2) + 2) || (argc < (min_required_params * 2) + 2)) {// each with its flag and mode flag, check the value of argc. If not enough parameters have been passed, inform user and exit. cout << "Usage is in one of few modes: < -pssm_cutoffs -CalcPSSM_Cutoff" << endl; // Inform the user of how to use the program - cout << "[2: CalcPSSM_Pval] "< -pssm_cutoffs -seq -out -NrandPSSM -CalcPSSM_Pval" << endl; // Inform the user of how to use the program + cout << "[1: CalcPSSM_Cutoff] " << argv[0] << " -pssm -pssm_cutoffs -CalcPSSM_Cutoff -total_memes " << endl; // Inform the user of how to use the program + cout << "[2: CalcPSSM_Pval] "< -pssm_cutoffs -seq -out -NrandPSSM -userFactor -CalcPSSM_Pval" << endl; // Inform the user of how to use the program cout << "[3: CalcPSSM_Hits] " << argv[0] << " -pssm -pssm_cutoffs -seq -out -CalcPSSM_Hits " << endl; // Inform the user of how to use the program cout << "\n\nThe number of provided arguments is "< -pssm_cutoffs \n"; // Inform the user of how to use the program + cout << "Usage is -pssm -pssm_cutoffs -total_memes \n"; // Inform the user of how to use the program exit(17); } cout << argv[0] <<" "; @@ -110,6 +112,10 @@ void getFileNamesFromArgv(int argc, char *argv[], string & PSSM_FileName, string * name of the program, which is stored in argv[0] */ if (string(argv[i]) == "-pssm") PSSM_FileName = string(argv[i + 1]); else if (string(argv[i]) == "-pssm_cutoffs") CutofsPerPSSM_FileName = string(argv[i + 1]); + else if (string(argv[i]) == "-total_memes") totalMemes = stoi(string(argv[i + 1])); + else if (string(argv[i]) == "-cutoff_random_peptitdes_percentile") PercentOfRandomHitsPerPSSM = stod(string(argv[i + 1])); + else if (string(argv[i]) == "-min_library_length_cutoff") minLibraryLength = stoi(string(argv[i + 1])); + else if (string(argv[i]) == "-max_library_length_cutoff") maxLibraryLength = stoi(string(argv[i + 1])); } } @@ -160,14 +166,57 @@ void getFileNamesFromArgv(int argc, char *argv[], string & PSSM_FileName, string cout< 12 argc , + //All params together -> 17 + // each with its flag and mode_flag, check the value of argc. If not enough parameters have been passed, inform user and exit. + if ((argc < (num_required_params + 3)) | (argc > (num_required_params + 8))){ + cout << "Usage is -pssm -pssm_cutoffs -seq -out \n"; // Inform the user of how to use the program + exit(12); + } + cout << argv[0] <<" "; + for (int i = 1; i < argc; ++i) // iterate over flags + { + /* We will iterate over argv[] to get the parameters stored inside. + * Note that we're starting on 1 because we don't need to know the + * name of the program, which is stored in argv[0] */ + if (string(argv[i]) == "-pssm") PSSM_FileName = string(argv[i + 1]); + else if (string(argv[i]) == "-pssm_cutoffs") CutofsPerPSSM_FileName = string(argv[i + 1]); + else if (string(argv[i]) == "-seq") Seq_FASTA_FileName = string(argv[i + 1]); + else if (string(argv[i]) == "-out") Hits_Out_FileName = string(argv[i + 1]); + else if (string(argv[i]) == "-NrandPSSM") numberOfRandomPSSM = size_t(atoi(argv[i + 1])); + else if (string (argv[i]) == "-useFactor") useFactor = true; + else if (string (argv[i]) == "-outputSequences") isOutputSequences=true; + else if (string (argv[i]) == "-sequenceHitMotifPath") sequenceHitMotifPath = string(argv[i + 1]); + else if (string (argv[i]) == "-useRpmFaaScanning") useRpmFaaScanning = true; + + cout< Seq_array; - readFileToSeq_array(Seq_FASTA_FileName, rpif._alph, Seq_array); + readFileToSeq_array(Seq_FASTA_FileName, rpif._alph, Seq_array, false, numberOfSeq); ofstream HitsReport_OutFileHandle; HitsReport_OutFileHandle.open(Hits_Out_FileName); @@ -299,8 +348,6 @@ void PSSM_scoresFromSeqVector(const PSSM& PSSM1, const vector > & } } - - vector PadSeq(const vector& seq, size_t PaddingLength) { vector tempSeqPadded(PaddingLength,GAP); @@ -312,11 +359,12 @@ vector PadSeq(const vector& seq, size_t PaddingLength) return tempSeqPadded; } -void readFileToSeq_array (const string fileName, alphabet& alph, vector &Seq_array) { +void readFileToSeq_array (const string fileName, alphabet& alph, vector &Seq_array, bool useRpmFaaScanning, int& numberOfSeq, bool isSetCopyNumber) { vector allLines; - size_t total_seq=0; + size_t total_seq = 0; fromFileToVectorOfString(fileName,allLines); for (size_t i=0; i &Se { string Seq; string name = currLine.substr(1); - + if (useRpmFaaScanning) { + auto lastIndex = currLine.find_last_of('_'); + count = stod(currLine.substr(lastIndex + 1)); + } ++i; currLine = allLines[i]; while (currLine.substr(0,1).compare(">")!=0) @@ -341,12 +392,13 @@ void readFileToSeq_array (const string fileName, alphabet& alph, vector &Se break; } } - SEQ currSeq(Seq, name, 1, alph); - total_seq++; + SEQ currSeq(Seq, name, count, alph, isSetCopyNumber); + total_seq += count; Seq_array.push_back(currSeq); i--; // got to new seq } } + numberOfSeq = total_seq; cout<<"Total Seq: "< get_sort_seq_vector_by_scores(vector & seq_vector, vector & scores_vector) { @@ -421,10 +469,26 @@ void get_top_hits(const vector & sorted_seq, double fraction, vector } } -double numberOfTotalHitsPerPSSM(const PSSM& pssm1, const vector & Seq_array, const size_t verbose=0) { +void writeSequenceHits(const PSSM& pssm1, const vector & hits, const string sequenceHitsPath){ + ofstream fileSequenceHit; + fileSequenceHit.open(sequenceHitsPath, std::ios_base::app); + fileSequenceHit << "MOTIF " << pssm1.PSSM_name << endl; + cout< & Seq_array, const string sequenceHitsPath, const bool isOutputSequences, const size_t verbose=0) { vector hits; Find_PSSM_Hits(pssm1, Seq_array, hits, verbose); //2 compute how many peptides are significant for this shuffled pssm double sum = 0; + //print the sequence that had hit with the motif + if (isOutputSequences) { + writeSequenceHits(pssm1, hits, sequenceHitsPath); + } for (size_t k = 0; k < hits.size(); ++k) { sum += hits[k]._seq._CopyNumber; } @@ -445,25 +509,39 @@ int assignPvalueToPSSMaRRAY(int argc, char *argv[]) string CutofsPerPSSM_FileName = ""; string Hits_Out_FileName = ""; size_t numberOfRandomPSSM = 0; - getFileNamesFromArgv(argc, argv, PSSM_FileName, CutofsPerPSSM_FileName, Seq_FASTA_FileName, Hits_Out_FileName,numberOfRandomPSSM); + bool useFactor = false; + bool isOutputSequences = false; + string sequenceHitMotifPath = ""; + bool useRpmFaaScanning = false; + int numberOfSeq = 0; + getFileNamesFromArgv(argc, argv, PSSM_FileName, CutofsPerPSSM_FileName, Seq_FASTA_FileName, Hits_Out_FileName, numberOfRandomPSSM, + useFactor, useRpmFaaScanning, isOutputSequences, sequenceHitMotifPath); + cout<<"Number of random PSSMs to calculate Pval: "<< numberOfRandomPSSM < Seq_array; - readFileToSeq_array(Seq_FASTA_FileName, rpif._alph, Seq_array); - + bool isSetCopyNumber = false; + readFileToSeq_array(Seq_FASTA_FileName, rpif._alph, Seq_array, useRpmFaaScanning, numberOfSeq, isSetCopyNumber); + double rpmFactorValue = double(1000000) / double(numberOfSeq); ofstream listOfPvaluesFile; listOfPvaluesFile.open(Hits_Out_FileName); - listOfPvaluesFile << "## PSSM_name\tp_Value\tTrue_Hits: num_of_hits" < numSigPeptides; + default_random_engine gen(483); // TODO seed should be fro input for (size_t j = 0; j < numberOfRandomPSSM; ++j) { - PSSM randomPSSM = rpif._PSSM_array[i].randomize(); //1 generate a random PPSM. - double sum = numberOfTotalHitsPerPSSM(randomPSSM, Seq_array,0); + PSSM randomPSSM = rpif._PSSM_array[i].randomize(gen); //1 generate a random PPSM. + double sum = numberOfTotalHitsPerPSSM(randomPSSM, Seq_array, sequenceHitMotifPath, false, 0); + if (useFactor & !useRpmFaaScanning & numberOfSeq != 0) { + sum *= rpmFactorValue; + } numSigPeptides.push_back(sum);// store the number } sort(numSigPeptides.begin(), numSigPeptides.end()); @@ -479,8 +557,9 @@ int assignPvalueToPSSMaRRAY(int argc, char *argv[]) } if (place == -1) place = 0; //so that we get p value = 1 in this case. //cout << "place = " << place << endl; - double p_Value = (numberOfRandomPSSM - place +0.0) / numberOfRandomPSSM; - listOfPvaluesFile << rpif._PSSM_array[i].PSSM_name << "\t" << p_Value << "\tTrue_Hits: " << numberOfHitsInRealPSSM < &characterFrequencies, size_t numberOfSeqToSimulate, size_t sequenceLength) : _characterFrequencies(characterFrequencies), _numberOfSeqToSimulate(numberOfSeqToSimulate), _sequenceLength(sequenceLength),_CysLoop (0) { // deafualt constroctor - NoCysLoop - srand(static_cast(time(NULL))); +randomPeptides::randomPeptides(vector &characterFrequencies, size_t numberOfSeqToSimulate, size_t sequenceLength) : + _characterFrequencies(characterFrequencies), _numberOfSeqToSimulate(numberOfSeqToSimulate), _sequenceLength(sequenceLength),_CysLoop (0) { // deafualt constroctor - NoCysLoop + // srand(static_cast(time(NULL))); // srand is set using seed at a higher level } -randomPeptides::randomPeptides(vector &characterFrequencies, size_t numberOfSeqToSimulate, size_t sequenceLength,bool CysLoop) : _characterFrequencies(characterFrequencies), _numberOfSeqToSimulate(numberOfSeqToSimulate), _sequenceLength(sequenceLength),_CysLoop (CysLoop) { - srand(static_cast(time(NULL))); +randomPeptides::randomPeptides(vector &characterFrequencies, size_t numberOfSeqToSimulate, size_t sequenceLength,bool CysLoop) : + _characterFrequencies(characterFrequencies), _numberOfSeqToSimulate(numberOfSeqToSimulate), _sequenceLength(sequenceLength),_CysLoop (CysLoop) { + // srand(static_cast(time(NULL))); // srand is set using seed at a higher level } + void randomPeptides::generateRandomSequences() { // std::mt19937 Seq_eng; // a core engine class // std::random_device dev_random; diff --git a/README.md b/README.md new file mode 100644 index 0000000..a1bdf01 --- /dev/null +++ b/README.md @@ -0,0 +1,229 @@ +# IgOme Profiling pipeline + +## Overview +IgOme Profiling pipeline is ... + +## Getting started + +### OS +The pipeline runs on Linux environment only. +For Windows the pipeline can run using [WSL and Visual Studio Code](https://code.visualstudio.com/docs/remote/wsl-tutorial). + +Tested on Ubuntu 18.04 via Windows. + +### Prerequisites +The following are required in order to run the code: +* git, e.g.: + ```bash + sudo apt install git + ``` +* Python 3.6+, .e.g: + ```bash + sudo apt install python3 python3-pip python3-venv + ``` +* g++, e.g.: + ``` + sudo apt install g++ + ``` +* CD-HIT: + * Install using instructions: http://weizhongli-lab.org/cd-hit/ + * Make sure cd-hit is installed globally, e.g. /usr/local/bin + * Test by running: + ```bash + # From project directory + cd-hit -h + ``` +* MAFFT: + * Install using instructions: https://mafft.cbrc.jp/alignment/software/ + * Make sure mafft is installed globally, e.g. /usr/bin + * Test by running: + ```bash + # From project directory + mafft -h + ``` + +It is advised to update the packages manager before installing, e.g.: +```bash +sudo apt update +``` + +## Repository +Configure git user and email (if not done yet): +```bash +git config --global user.name "FIRST_NAME LAST_NAME" +git config --global user.email "you@mail.com" +``` + +Clone the repository: +```bash +# From projects/workspace directory +git clone https://github.com/Webiks/IgomeProfiling.git +cd IgomeProfiling +``` + +### Compile +Most of the code is in Python but some of the code is in C++ and requires compilation: +* UnitePSSMs: + ```bash + # From project directory + cd UnitePSSMs + g++ *.cpp -std=c++11 -O3 -o UnitePSSMs + ``` +* PSSM_score_Peptide: + ```bash + # From project directory + cd PSSM_score_Peptide + g++ *.cpp -std=c++11 -O3 -o PSSM_score_Peptide + ``` +* Hits + ```bash + # From project directory + cd hits_cpp + g++ *.cpp -std=c++11 -O3 -o hits + ``` +* TF-IDF/Merge + ```bash + # From project directory + cd tfidf + g++ *.cpp -std=c++11 -O3 -o tfidf + ``` + +### Python +Python3 should be installed in previous steps. +Use the following to install the required packages: +* Create a virtual environment for the project: + ```bash + # From project directory + python3 -m venv .venv + source .venv/bin/activate + ``` +* Install the dependencies: + ```bash + pip3 install -r requirements.txt + ``` + +## Running +The pipeline is modular, each step is a python file which can be run by itself by passing command line arguments. +To run the entire pipeline the entry point is ```IgOmeProfiling_pipeline.py```. +Run the pipeline using the following: +```bash +python3 IgOmeProfiling_pipeline.py fastq_file_path barcodes_to_samples_path samples_to_biological_conditions_path analysis_results_directory run_logs_directory +``` + +For more information and options run the following: +```bash +python3 IgOmeProfiling_pipeline.py -h +``` + +The code assumes existence of ```bash``` in order to run. +This can be changed via ```local_command_prefix``` variable in ```global_params.py```. + +## Important flags: +The following is are important flags, see help for details: +* left_construct: Depends on experiment, default is for EXP12 +* right_construct: Depends on experiment, default is for EXP12 +* allowed_gap_frequency: Threshold for gappy columns +* concurrent_cutoffs: If set then cutoffs are calculated concurrently, if not each BC run as a whole. +* meme_split_size: Batch size of memes for cutoffs (if concurrent_cutoffs is set) and pValues calculation. +* number_of_random_pssms: Number of random PSSMs for calculating pValues. + +## Testing +The code contains mock data for testing. + +Set configuration in ```global_params.py```. +To run local set: +* run_using_celery to False +* run_local_in_parallel_mode to True + +Run the following: +```bash +# From project directory +python3 IgOmeProfiling_pipeline.py mock_data/exp12_10M_rows.fastq.gz mock_data/barcode2samplename.txt mock_data/samplename2biologicalcondition.txt output/analysis output/logs +``` + +The entire pipeline might run a few hours on mock data on 96 cores. +The output of the pipeline are heatmaps of most important motifs per biological condition va all samples. + +## Docker +The code can be containerized using Docker: + +Set configuration in ```global_params.py```. +To run local set: +* run_using_celery to True if using multiple machines +* run_local_in_parallel_mode to True + +```bash +# From project directory +./build_docker.sh +``` + +The image is built without data and command argument of "-h". +To run the mock-data using docker: +```bash +# From project directory +docker run --name igome --rm -v ./some_data:/data -v ./test:/output webiks/igome-profile /data/exp12_10M_rows.fastq.gz /data/barcode2samplename.txt /data/samplename2biologicalcondition.txt /output/analysis /output/logs +``` + +The mock data is included in the docker, to run it: +```bash +docker run --name igome --rm -v ./test:/output webiks/igome-profile ./mock_data/exp12_10M_rows.fastq.gz ./mock_data/barcode2samplename.txt ./mock_data/samplename2biologicalcondition.txt /output/analysis /output/logs +``` + +Upload to AWS (using aws-cli with credentials set): +```bash +./aws_upload.sh +``` + +In AWS machine (with aws-cli credentials set): +```bash +$(aws ecr get-login --no-include-email --region us-west-2) +docker pull 686447933053.dkr.ecr.us-west-2.amazonaws.com/igome-profile:latest +docker run --name igome --rm -v ./test:/output 686447933053.dkr.ecr.us-west-2.amazonaws.com/igome-profile:latest ./mock_data/exp12_10M_rows.fastq.gz ./mock_data/barcode2samplename.txt ./mock_data/samplename2biologicalcondition.txt /output/analysis /output/logs +``` + +## Running on multiple machines +Running on multiple machines is support via [Celery](http://www.celeryproject.org/) and [RabbitMQ](https://www.rabbitmq.com/). +Pipeline steps synchronize via files. Therefore, the same paths must be mounted on all machines. For example if using AWS mount an EFS to /data on all machines. + +Run RabbitMQ + Flower via docker-compose: +```yaml +version: '3' + +services: + rabbit: + image: rabbitmq:management + restart: always + ports: + - 5672:5672 + - 15672:15672 + flower: + image: mher/flower + restart: always + command: flower --broker=pyamqp://guest@rabbit// + ports: + - 5555:5555 +``` +Open [RabbitMQ Management](http://localhost:15672) and/or [Flower](http://localhost:5555) to monitor the pipeline tasks. + +On a worker instance: +* Mount shared drive to directory, e.g. /data +* Pull worker image from repository +* Run the worker + ```bash + docker run -d --restart always -e CELERY_BROKER_URL="pyamqp://guest@[broker-ip]//" -v /data:/data --name igomeworker 686447933053.dkr.ecr.us-west-2.amazonaws.com/igome-profile-worker:latest + ``` + * Replace [broker-ip] and guest with RabbitMQ host and user/password (if defined). + * Change volume mount to shared drive + +Run the pipeline of some machine: +* Mount shared drive, e.g. /data +* Pull pipeline image from repository +* Run the pipeline, e.g. + ```bash + docker run --name igome --rm -e CELERY_BROKER_URL="pyamqp://guest@[broker-ip]//" -d -v /data:/data 686447933053.dkr.ecr.us-west-2.amazonaws.com/igome-profile /data/mock_data/exp12_10M_rows.fastq.gz /data/mock_data/barcode2samplename.txt /data/mock_data/samplename2biologicalcondition.txt /data/output/analysis /data/output/logs + ``` +* Use ```docker logs --tail 10 -f igome``` to see over all progress + + +## License +TBD diff --git a/auxiliaries/file_writer.py b/auxiliaries/file_writer.py old mode 100755 new mode 100644 diff --git a/auxiliaries/pipeline_auxiliaries.py b/auxiliaries/pipeline_auxiliaries.py index 41a3659..117047b 100644 --- a/auxiliaries/pipeline_auxiliaries.py +++ b/auxiliaries/pipeline_auxiliaries.py @@ -1,266 +1,607 @@ -import os -import sys -from subprocess import call, run -from time import time, sleep -import global_params -import logging - -logger = logging.getLogger('main') -logging.basicConfig(level=logging.INFO) - - -nnk_table: {str: str} = {"CGT": "R", "CGG": "R", "AGG": "R", - "CTT": "L", "CTG": "L", "TTG": "L", - "TCT": "S", "TCG": "S", "AGT": "S", - "GCT": "A", "GCG": "A", - "GGT": "G", "GGG": "G", - "CCT": "P", "CCG": "P", - "ACT": "T", "ACG": "T", - "CAG": "Q", - "TAG": "Q", # rather than q - "GTT": "V", "GTG": "V", - "AAT": "N", - "GAT": "D", - "TGT": "C", - "GAG": "E", - "CAT": "H", - "ATT": "I", - "AAG": "K", - "ATG": "M", - "TTT": "F", - "TGG": "W", - "TAT": "Y"} - -def verify_file_is_not_empty(file_path): - import logging - logger = logging.getLogger('main') - # make sure that there are results and the file is not empty - with open(file_path) as f: - if len(f.read(10).strip()) == 0: - # TODO: write error to a global error file - msg = f'Input file is empty {file_path}' - logger.error(msg) - raise RuntimeError(msg) - - -def load_fasta_to_dict(fasta_path, reverse=False, upper_keys=False, upper_values=False): - """ - :param fasta_path: a path to a FASTA file - :return: a dictionary that maps each header (without ">" and rstriped()) to its corresponding sequence (rstriped()) - an int that represent the number of sequences - an int that represent the length of the alignment - """ - key2value = {} - with open(fasta_path) as f: - for i, header in enumerate(f): - if not header.startswith('>'): - raise TypeError(f'Illegal fasta file. Illegal record is record number {i} is\n{header}') - # returns header without ">" ! - if not reverse: - key = header[1:].rstrip() - value = f.readline().rstrip() - if key in key2value: - raise ValueError(f'{key} already appears in dict!!\n' - f'old entry: {key}:{key2value[key]}\n' - f'new entry: {key}:{value}') - key2value[key] = value - else: - key = f.readline().rstrip() - value = header[1:].rstrip() - if key in key2value: - raise ValueError(f'{key} already appears in dict!!\n' - f'old entry: {key}:{key2value[key]}\n' - f'new entry: {key}:{value}') - key2value[key] = value - if not reverse: - return key2value, len(key2value), len(key2value[header[1:].rstrip()]) - else: - return key2value, len(key2value) - -def measure_time(total): - hours = total // 3600 - minutes = (total % 3600) // 60 - seconds = total % 60 - if hours != 0: - return f'{hours}:{minutes:02}:{seconds:02} hours' - elif minutes != 0: - return f'{minutes}:{seconds:02} minutes' - else: - return f'{seconds} seconds' - - -def wait_for_results(script_name, path, num_of_expected_results, error_file_path, example_cmd='', suffix='done', - remove=False, time_to_wait=10, start=0, done_files_list=None): - """ - :param script_name: - :param path: - :param num_of_expected_results: - :param error_file_path: - :param example_cmd: - :param suffix: - :param remove: - :param time_to_wait: - :param start: - :param done_files_list: - :return: waits until path contains num_of_expected_results $suffix files - """ - # if True: return - if not start: - start = time() - logger.info(f'Waiting for {script_name}...\nContinues when {num_of_expected_results} results with suffix="{suffix}" will be in:\n{path}') - if example_cmd: - logger.info(f'An example command looks like this:\n{example_cmd}\n\n') - if num_of_expected_results==0: - logger.fatal(f'\n{"#"*100}\nnum_of_expected_results in {path} is {num_of_expected_results}!\nSomething went wrong in the previous step...\n{"#"*100}') - #raise ValueError(f'\n{"#"*100}\nnum_of_expected_results is {num_of_expected_results}! Something went wrong in the previous step...\n{"#"*100}') - total_time = 0 - i = 0 - current_num_of_results = 0 - while current_num_of_results < num_of_expected_results: - sleep(time_to_wait) - current_num_of_results = sum(1 for x in os.listdir(path) if x.endswith(suffix)) - jobs_left = num_of_expected_results - current_num_of_results - total_time += time_to_wait - i += 1 - if i % 5 == 0: # print status every 5 cycles of $time_to_wait - logger.info(f'\t{measure_time(total_time)} have passed since started waiting ({num_of_expected_results} - {current_num_of_results} = {jobs_left} more files are still missing)') - if done_files_list: - logger.info(f'This are the files the are still missing:\n{[file for file in done_files_list if not os.path.exists(file)]}') - assert not os.path.exists(error_file_path), f'An error occurred. For further details see: {error_file_path}' - - if remove: - # execute(['python', '-u', '/groups/pupko/orenavr2/pipeline/RemoveDoneFiles.py', path, suffix]) - call(f'rm {path}/*{suffix}', shell=True) - - end = time() - logger.info(f'{path} contains {current_num_of_results} done files!') - logger.info(f'Done waiting for: {script_name}\n(took {measure_time(int(end-start))}).\n') - assert not os.path.exists(error_file_path), f'An error occurred. For further details see: {error_file_path}' - - -def submit_pipeline_step(script_path, params_lists, tmp_dir, job_name, queue_name, verbose, new_line_delimiter='!@#', - q_submitter_script_path='/bioseq/bioSequence_scripts_and_constants/q_submitter_power.py', - required_modules_as_list=None, num_of_cpus=1): - - required_modules_as_str = 'python/python-anaconda3.6.5-orenavr2' - if required_modules_as_list: - # don't forget a space after the python module!! - required_modules_as_str += ' ' + ' '.join(required_modules_as_list) - cmds_as_str = f'module load {required_modules_as_str}' - # the queue does not like very long commands so I use a dummy delimiter (!@#) to break the rows in q_submitter - cmds_as_str += new_line_delimiter - - for params in params_lists: - cmds_as_str += ' '.join(['python', script_path, *[str(param) for param in params]] + (['-v'] if verbose else [])) + ';' - cmds_as_str += new_line_delimiter - - example_cmd = ' '.join(['python', script_path, *[str(param) for param in params]] + (['-v'] if verbose else [])) + ';' - - # GENERATE DONE FILE - # write an empty string (like "touch" command) - # cmds_as_str += ' '.join(['python', done_files_script_path, os.path.join(tmp_dir, job_name + '.done'), 'done'])+';' - # cmds_as_str += new_line_delimiter - - cmds_as_str += '\t' + job_name + '\n' - cmds_path = os.path.join(tmp_dir, f'{job_name}.cmds') - if os.path.exists(cmds_path): - cmds_path = os.path.join(tmp_dir, f'{job_name}_{time()}.cmds') - with open(cmds_path, 'w') as f: - f.write(cmds_as_str) - - # process_str = f'{q_submitter_script_path} {cmds_path} {tmp_dir} -q {queue_name} --cpu {num_of_cpus}' - process = [q_submitter_script_path, cmds_path, tmp_dir, '-q', queue_name, '--cpu', str(num_of_cpus)] - logger.info(f'Calling:\n{" ".join(process)}') - # if True: return - run(process) - return example_cmd - - -def fetch_cmd(script_name, parameters, verbose, error_path): - cmd = f'python3 {script_name} ' + ' '.join(parameters + (['-v'] if verbose else [])) - logger.info(f'Executing:\n{cmd}') - # try: - run(cmd, shell=True) - # logger.info(f'Finished:\n{cmd}') - # except Exception as e: - # fail(error_path, e) - - - -def load_table_to_dict(table_path, error_msg, delimiter ='\t'): - table = {} - with open(table_path) as f: - for line in f: - if line.isspace(): # empty line - continue - key, value = line.strip().split(delimiter) - if key in table: - assert False, error_msg.replace('{}', key) # TODO: write to a global error log file - table[key] = value - return table - - -def fail(error_path, e): - exc_type, exc_obj, exc_tb = sys.exc_info() - fname = os.path.split(exc_tb.tb_frame.f_code.co_filename)[1] - with open(error_path, 'w') as f: - f.write(f'\n{"$"*100}\n\n{fname}: {exc_type}, ' - f'at line: {exc_tb.tb_lineno}\n\ne.args: {e.args}\n\n{"$"*100}') - raise e - - -def get_cluster_rank_from(header): - return int(header.split('clusterRank_')[-1].split('_')[0]) - - -def get_unique_members_from(header): - unique_members = header.split('uniqueMembers_')[-1].split('_')[0] - if unique_members.startswith('top'): - unique_members = unique_members[3:] - return int(unique_members) - - -def get_cluster_size_from_name(path): - return float(os.path.splitext(path)[0].split('clusterSize_')[1]) - - -def get_count_from(header): - # e.g., >seq_1_lib_C10C_len_12_counts_325350.363668618 - if 'Type' not in header: - return float(header.split('_')[-1]) - # e.g., >1_Length_12_Repeats_318612.4098079804_Type_C10C - return float(header.split('_')[-3]) - - -def get_configuration_from(header): - if 'Type' not in header: - # e.g., >seq_1_lib_C10C_len_12_counts_325350.363668618 - return header.split('_')[3] - # e.g., >1_Length_12_Repeats_318612.4098079804_Type_C10C - return header.split('_')[-1] - - -def remove_redundant_newlines_from_fasta(input_file_path, output_file_path): - # remove redundant newlines (MAFFT puts only 100 chars per line), i.e., this: - #>seq_1_lib_C10C_len_12_counts_297739.4827131018 - #----------------------C--------------H------GKTGASFL----Q--- - #C--------------------- - # will turn into this: - #>seq_1_lib_C10C_len_12_counts_297739.4827131018 - #----------------------C--------------H------GKTGASFL----Q---C--------------------- - result = '' - sequence = '' - with open(input_file_path) as f: - for line in f: - line = line.rstrip() - if line.startswith('>'): - if sequence != '': # not the first header - result += f'{sequence}\n' - sequence = '' - result += f'{line}\n' - else: - sequence += line - result += f'{sequence}\n' - - with open(output_file_path, 'w') as f: - f.write(result) \ No newline at end of file +import os +import sys +from subprocess import call, run, Popen, PIPE +from time import time, sleep +import global_params +import json +import jsonschema +import numpy as np +import logging +logger = logging.getLogger('main') +logging.basicConfig(level=logging.INFO) +import seaborn as sns +import matplotlib.pyplot as plt + +nnk_table: {str: str} = {"CGT": "R", "CGG": "R", "AGG": "R", + "CTT": "L", "CTG": "L", "TTG": "L", + "TCT": "S", "TCG": "S", "AGT": "S", + "GCT": "A", "GCG": "A", + "GGT": "G", "GGG": "G", + "CCT": "P", "CCG": "P", + "ACT": "T", "ACG": "T", + "CAG": "Q", + "TAG": "Q", # rather than q + "GTT": "V", "GTG": "V", + "AAT": "N", + "GAT": "D", + "TGT": "C", + "GAG": "E", + "CAT": "H", + "ATT": "I", + "AAG": "K", + "ATG": "M", + "TTT": "F", + "TGG": "W", + "TAT": "Y"} + + +schema_sample2bc = { + "type": "object", + "propertyNames": { + "pattern": "^[A-Za-z0-9_]+$" + }, + "patternProperties": { + "^[A-Za-z0-9_]+$" : { "type": "array", "items": { "type": "string", "pattern": "^[A-Za-z0-9_]+$" } } + } +} + + +schema_cross_exp = { + "type": "object", + "properties": { + "configuration": { + "description": "params that relevent for all runs", + "type": "object" + }, + "runs": { + "description": "A specific run name and is params", + "type": "object", + "patternProperties": { + "^[A-Za-z0-9_+]+$": { + "type": "object", + "properties": { + "configuration": { + "type": "object", + "properties": { + "reads_path": { "type": "string" }, + "motifs_path": { "type": "string" }, + "model_path": { "type": "string" }, + "done_file_path": { "type": "string" }, + "logs_dir": { "type": "string" } + }, + "required": [ "reads_path", "motifs_path", "model_path", "done_file_path", "logs_dir" ] + }, + "sample2bc": {"type": "object"}, + "biological_motifs_combine": {"type": "object"} + }, + "required": [ "configuration", "sample2bc" ], + }, + + }, + "additionalProperties": False + }, + }, + "required": [ "configuration", "runs" ] +} + + +schema_inference = { + "type": "object", + "properties": { + "configuration": { + "description": "params that relevent for all runs", + "type": "object" + }, + "runs": { + "description": "A specific run name and is params", + "type": "object", + "patternProperties": { + "^[A-Za-z0-9_+]+$": { + "properties": { + "reads_path": {"type":"string"}, + "motifs_path": {"type":"string"}, + "sample2bc": {"type": "string"}, + "done_file_path": {"type":"string"}, + "logs_dir": { "type": "string" } + }, + "required": ["reads_path", "motifs_path", "sample2bc", "done_file_path", "logs_dir" ], + } + }, + "additionalProperties": False + } + }, + "required": [ "configuration", "runs" ] +} + + +schema_reads = { + "type": "object", + "properties": { + "configuration": { + "description": "params that relevent for all runs", + "type": "object" + }, + "runs": { + "description": "A specific run name and is params", + "type": "object", + "patternProperties": { + "^[A-Za-z0-9_+]+$": { + "required": [ "fastq", "barcode2sample", "done_file_path", "reads_path", "logs_dir" ], + "properties": { + "fastq": { "type": "string" }, + "barcode2sample": { "type": "string" }, + "done_file_path": { "type": "string" }, + "reads_path": { "type": "string" }, + "logs_dir": { "type": "string" } + } + } + }, + "additionalProperties": False + } + }, + "required": ["configuration", "runs" ] +} + + +schema_to_phase = { + 'reads_filtration': schema_reads, + 'motif_inference': schema_inference, + 'model_fitting': schema_cross_exp +} + + +def verify_file_is_not_empty(file_path): + import logging + from time import sleep + logger = logging.getLogger('main') + total_retries = 2 # +1 including first try + retries_count = 0 + sleep_time = 2 + sleep_factor = 2 + # make sure that there are results and the file is not empty + with open(file_path) as f: + if len(f.read(10).strip()) == 0: + # TODO: write error to a global error file + if retries_count == total_retries: + msg = f'Input file is empty {file_path}' + logger.error(msg) + raise RuntimeError(msg) + else: + logger.warn(f'Input file is empty {file_path}, retrying in {sleep_time} seconds ({retries_count}/{total_retries})') + retries_count += 1 + sleep(sleep_time) + sleep_time *= sleep_factor + return + + +def load_fasta_to_dict(fasta_path, reverse=False, upper_keys=False, upper_values=False): + """ + :param fasta_path: a path to a FASTA file + :return: a dictionary that maps each header (without ">" and rstriped()) to its corresponding sequence (rstriped()) + an int that represent the number of sequences + an int that represent the length of the alignment + """ + key2value = {} + with open(fasta_path) as f: + for i, header in enumerate(f): + if not header.startswith('>'): + raise TypeError(f'Illegal fasta file. Illegal record is record number {i} is\n{header}') + # returns header without ">" ! + if not reverse: + key = header[1:].rstrip() + value = f.readline().rstrip() + if key in key2value: + raise ValueError(f'{key} already appears in dict!!\n' + f'old entry: {key}:{key2value[key]}\n' + f'new entry: {key}:{value}') + key2value[key] = value + else: + key = f.readline().rstrip() + value = header[1:].rstrip() + if key in key2value: + raise ValueError(f'{key} already appears in dict!!\n' + f'old entry: {key}:{key2value[key]}\n' + f'new entry: {key}:{value}') + key2value[key] = value + if not reverse: + return key2value, len(key2value), len(key2value[header[1:].rstrip()]) + else: + return key2value, len(key2value) + +def measure_time(total): + hours = total // 3600 + minutes = (total % 3600) // 60 + seconds = total % 60 + if hours != 0: + return f'{hours}:{minutes:02}:{seconds:02} hours' + elif minutes != 0: + return f'{minutes}:{seconds:02} minutes' + else: + return f'{seconds} seconds' + + +def wait_for_results(script_name, path, num_of_expected_results, error_file_path, example_cmd='', suffix='done', + remove=False, time_to_wait=10, start=0, done_files_list=None): + """ + :param script_name: + :param path: + :param num_of_expected_results: + :param error_file_path: + :param example_cmd: + :param suffix: + :param remove: + :param time_to_wait: + :param start: + :param done_files_list: + :return: waits until path contains num_of_expected_results $suffix files + """ + # if True: return + if not start: + start = time() + logger.info(f'Waiting for {script_name}...\nContinues when {num_of_expected_results} results with suffix="{suffix}" will be in:\n{path}') + if example_cmd: + logger.info(f'An example command looks like this:\n{example_cmd}\n\n') + if num_of_expected_results==0: + logger.fatal(f'\n{"#"*100}\nnum_of_expected_results in {path} is {num_of_expected_results}!\nSomething went wrong in the previous step...\n{"#"*100}') + #raise ValueError(f'\n{"#"*100}\nnum_of_expected_results is {num_of_expected_results}! Something went wrong in the previous step...\n{"#"*100}') + total_time = 0 + i = 0 + current_num_of_results = 0 + while current_num_of_results < num_of_expected_results: + sleep(time_to_wait) + current_num_of_results = sum(1 for x in os.listdir(path) if x.endswith(suffix)) + jobs_left = num_of_expected_results - current_num_of_results + total_time += time_to_wait + i += 1 + if i % 5 == 0: # print status every 5 cycles of $time_to_wait + logger.info(f'\t{measure_time(total_time)} have passed since started waiting ({num_of_expected_results} - {current_num_of_results} = {jobs_left} more files are still missing)') + if done_files_list: + logger.info(f'This are the files the are still missing:\n{[file for file in done_files_list if not os.path.exists(file)]}') + assert not os.path.exists(error_file_path), f'An error occurred. For further details see: {error_file_path}' + + if remove: + # execute(['python', '-u', '/groups/pupko/orenavr2/pipeline/RemoveDoneFiles.py', path, suffix]) + call(f'rm {path}/*{suffix}', shell=True) + + end = time() + logger.info(f'{path} contains {current_num_of_results} done files!') + logger.info(f'Done waiting for: {script_name}\n(took {measure_time(int(end-start))}).\n') + assert not os.path.exists(error_file_path), f'An error occurred. For further details see: {error_file_path}' + + +def submit_pipeline_step_to_cluster(script_path, params_lists, tmp_dir, job_name, queue_name, verbose, new_line_delimiter='!@#', + q_submitter_script_path='/bioseq/bioSequence_scripts_and_constants/q_submitter_power.py', + required_modules_as_list=None, num_of_cpus=1, executable='python', done_path=None): + required_modules_as_str = 'python/python-anaconda3.6.5-orenavr2' + if required_modules_as_list: + # don't forget a space after the python module!! + required_modules_as_str += ' ' + ' '.join(required_modules_as_list) + cmds_as_str = f'module load {required_modules_as_str}' + # the queue does not like very long commands so I use a dummy delimiter (!@#) to break the rows in q_submitter + cmds_as_str += new_line_delimiter + + if executable is None: + executable = '' + + for params in params_lists: + cmds_as_str += ' '.join([executable, script_path, *[str(param) for param in params]] + (['-v'] if verbose else [])).lstrip() + ';' + cmds_as_str += new_line_delimiter + + example_cmd = ' '.join([executable, script_path, *[str(param) for param in params]] + (['-v'] if verbose else [])).lstrip() + ';' + + # GENERATE DONE FILE + # write an empty string (like "touch" command) + # cmds_as_str += ' '.join(['python', done_files_script_path, os.path.join(tmp_dir, job_name + '.done'), 'done'])+';' + # cmds_as_str += new_line_delimiter + + cmds_as_str += '\t' + job_name + '\n' + cmds_path = os.path.join(tmp_dir, f'{job_name}.cmds') + if os.path.exists(cmds_path): + cmds_path = os.path.join(tmp_dir, f'{job_name}_{time()}.cmds') + with open(cmds_path, 'w') as f: + f.write(cmds_as_str) + + # process_str = f'{q_submitter_script_path} {cmds_path} {tmp_dir} -q {queue_name} --cpu {num_of_cpus}' + process = [q_submitter_script_path, cmds_path, tmp_dir, '-q', queue_name, '--cpu', str(num_of_cpus)] + logger.info(f'Calling:\n{" ".join(process)}') + # if True: return + run(process) + return example_cmd + + +def build_args(executable, script_path, params, verbose): + args = [] + if executable: + args.append(executable) + if script_path: + args.append(script_path) + args += [str(x) for x in params] + if verbose: + args.append('-v') + return ' '.join(args) + + +def create_command(script_path, params_lists, tmp_dir, job_name, queue_name, verbose, new_line_delimiter='!@#', + q_submitter_script_path='/bioseq/bioSequence_scripts_and_constants/q_submitter_power.py', + required_modules_as_list=None, num_of_cpus=1, executable='python', done_path=None): + new_line_delimiter = '\n' + cmds_as_str = '' + + for params in params_lists: + cmds_as_str += build_args(executable, script_path, params, verbose) + cmds_as_str += new_line_delimiter + + example_cmd = build_args(executable, script_path, params, verbose) + + cmds_path = os.path.join(tmp_dir, f'{job_name}.cmds') + if os.path.exists(cmds_path): + cmds_path = os.path.join(tmp_dir, f'{job_name}_{time()}.cmds') + with open(cmds_path, 'w') as f: + f.write(cmds_as_str) + + if global_params.local_command_prefix: + process = f'{global_params.local_command_prefix} {cmds_path}' + else: + process = cmds_path + return process, example_cmd + + +def submit_pipeline_step_to_celery(script_path, params_lists, tmp_dir, job_name, queue_name, verbose, new_line_delimiter='!@#', + q_submitter_script_path='/bioseq/bioSequence_scripts_and_constants/q_submitter_power.py', + required_modules_as_list=None, num_of_cpus=1, executable='python', done_path=None): + process, example_cmd = create_command(**locals()) + from worker import submit + logger.info(f'Calling using celery:\n{process}') + submit.delay(process, shell=True) + return example_cmd + + +def run_step_locally(script_path, params_lists, tmp_dir, job_name, queue_name, verbose, new_line_delimiter='!@#', + q_submitter_script_path='/bioseq/bioSequence_scripts_and_constants/q_submitter_power.py', + required_modules_as_list=None, num_of_cpus=1, executable='python', done_path=None): + process, example_cmd = create_command(**locals()) + logger.info(f'Calling:\n{process}') + if global_params.run_local_in_parallel_mode: + Popen(process, shell=True) + else: + run(process, shell=True) + return example_cmd + + +def submit_pipeline_step(script_path, params_lists, tmp_dir, job_name, queue_name, verbose, new_line_delimiter='!@#', + q_submitter_script_path='/bioseq/bioSequence_scripts_and_constants/q_submitter_power.py', + required_modules_as_list=None, num_of_cpus=1, executable='python', done_path = None): + if done_path and os.path.exists(done_path): + process, example_cmd = create_command(**locals()) + logger.info(f'Skipping "{example_cmd}" as "{done_path}" exists') + return example_cmd + if global_params.run_using_celery: + return submit_pipeline_step_to_celery(**locals()) + if global_params.is_run_on_cluster: + return submit_pipeline_step_to_cluster(**locals()) + + return run_step_locally(**locals()) + + +def fetch_cmd(script_name, parameters, verbose, error_path, done_path=None): + cmd = f'python3 {script_name} ' + ' '.join(parameters + (['-v'] if verbose else [])) + if done_path and os.path.exists(done_path): + logger.info(f'Skipping "{cmd}" as "{done_path}" exists') + return + logger.info(f'Executing:\n{cmd}') + # try: + run(cmd, shell=True) + # logger.info(f'Finished:\n{cmd}') + # except Exception as e: + # fail(error_path, e) + + +def load_table_to_dict(table_path, error_msg, delimiter ='\t', is_validate_json = False): + table = {} + filename, file_extension = os.path.splitext(table_path) + if file_extension == '.txt': + with open(table_path) as f: + for line in f: + if line.isspace(): # empty line + continue + if line.startswith('#'): # ignore symbol + continue + key, value = line.strip().split(delimiter) + if key in table: + message = error_msg.replace('{}', key) + logger.error(message) + assert False, message + table[key] = value + else: + try: + json_data = json.load(open(table_path)) + except: + assert False, f'"{table_path}" is not a valid JSON file' + if is_validate_json: + is_valid_json = is_valid_json_structure(table_path, json_data, schema_sample2bc, logger) + if not is_valid_json: + message = 'Invalid JSON file structure, expected dictionary of key/value strings' + logger.error(message) + assert False, message + for key in json_data: + for val in json_data[key]: + if val in table: + message = error_msg.replace('{}', val) + logger.error(message) + assert False, message + table[val] = key + return table + + +def fail(error_path, e): + exc_type, exc_obj, exc_tb = sys.exc_info() + fname = os.path.split(exc_tb.tb_frame.f_code.co_filename)[1] + with open(error_path, 'w') as f: + f.write(f'\n{"$"*100}\n\n{fname}: {exc_type}, ' + f'at line: {exc_tb.tb_lineno}\n\ne.args: {e.args}\n\n{"$"*100}') + raise e + + +def get_cluster_rank_from(header): + return int(header.split('clusterRank_')[-1].split('_')[0]) + + +def get_unique_members_from(header): + unique_members = header.split('uniqueMembers_')[-1].split('_')[0] + if unique_members.startswith('top'): + unique_members = unique_members[3:] + return int(unique_members) + + +def get_cluster_size_from_name(path): + # e.g., CGGLKGAPFLAC_17b_03_clusterRank_0000_uniqueMembers_167_clusterSize_343425.46.faa + return float(os.path.splitext(path)[0].split('clusterSize_')[1]) + + +def get_samples_from_name(path): + # e.g., CGGLKGAPFLAC_17b_03_clusterRank_0000_uniqueMembers_167_clusterSize_343425.46.faa + path_without_suffix = os.path.splitext(path)[0] + split_path = path_without_suffix.split('_') + index_of_clusterRank = 0 + for part_path in split_path: + if 'clusterRank' in part_path: + break + index_of_clusterRank += 1 + sample_name = '_'.join(split_path[1:index_of_clusterRank]) + return sample_name + + +def get_unique_members_from_name(path): + # e.g., CGGLKGAPFLAC_17b_03_clusterRank_0000_uniqueMembers_167_clusterSize_343425.46.faa + return float(os.path.splitext(path)[0].split('uniqueMembers_')[1].split('_')[0]) + + +def get_count_from(header): + # e.g., >seq_1_lib_C10C_len_12_counts_325350.363668618 + if 'Type' not in header: + return float(header.split('_')[-1]) + # e.g., >1_Length_12_Repeats_318612.4098079804_Type_C10C + return float(header.split('_')[-3]) + + +def get_configuration_from(header): + if 'Type' not in header: + # e.g., >seq_1_lib_C10C_len_12_counts_325350.363668618 + return header.split('_')[3] + # e.g., >1_Length_12_Repeats_318612.4098079804_Type_C10C + return header.split('_')[-1] + + +def remove_redundant_newlines_from_fasta(input_file_path, output_file_path): + # remove redundant newlines (MAFFT puts only 100 chars per line), i.e., this: + #>seq_1_lib_C10C_len_12_counts_297739.4827131018 + #----------------------C--------------H------GKTGASFL----Q--- + #C--------------------- + # will turn into this: + #>seq_1_lib_C10C_len_12_counts_297739.4827131018 + #----------------------C--------------H------GKTGASFL----Q---C--------------------- + result = '' + sequence = '' + with open(input_file_path) as f: + for line in f: + line = line.rstrip() + if line.startswith('>'): + if sequence != '': # not the first header + result += f'{sequence}\n' + sequence = '' + result += f'{line}\n' + else: + sequence += line + result += f'{sequence}\n' + + with open(output_file_path, 'w') as f: + f.write(result) + + +def count_memes(path): + p = Popen(f'cat {path} | grep MOTIF | wc -l', stdout=PIPE, shell=True) + (output, err) = p.communicate() + p_status = p.wait() + count = int(output) if p_status == 0 else 0 + print(f'Found {count} memes in {path}') + return count + + +def is_valid_json_structure(json_path, json_data, schema, logger): + try: + jsonschema.validate(instance=json_data, schema=schema) + except jsonschema.exceptions.ValidationError as err: + logger.error(f'The structure of "{json_path}" file is not valid, reason: {err}') + return False + return True + + +def log_scale(df, rank_method): + # The options of ranking method are - hits, controlled_shuffles, pval, tfidf + if rank_method == 'hits': + df = np.log2(df + 1) + if rank_method == 'pval': + df = 1-df + if rank_method == 'tfidf': + df = -np.log2(df + 0.0001) # avoid 0 + return df + + +def change_key_name(old_names_dict, map_name, reverse = False): + new_dict = {} + if old_names_dict: + keys = old_names_dict.keys() + for key in keys: + new_dict[map_name[key]] = old_names_dict[key] + return new_dict + return old_names_dict + + +def process_params(args, config_path, map_name_parameters, func_run, phase, argv): + # create data structure for running filter_reads + done_file = args.done_file_path + base_map = args.__dict__ + keys = base_map.keys() + base_map = change_key_name(base_map, map_name_parameters) + if config_path: + f = open(config_path) + multi_experiments_dict = json.load(f) + # validation of the json file + is_valid = is_valid_json_structure(config_path, multi_experiments_dict, schema_to_phase[phase], logger) + if not is_valid: + return + configuration = multi_experiments_dict['configuration'] + base_map.update(configuration) + runs = multi_experiments_dict['runs'] + for run in runs: + dict_params = base_map.copy() + if phase == 'model_fitting': + dict_params.update(runs[run]['configuration']) + else: + dict_params.update(runs[run]) + # create new list of argv of the specific run. + get_key = lambda k: k + get_value = lambda k: str(dict_params[map_name_parameters[k]]) + has_value = lambda k: str(dict_params[map_name_parameters[k]]) if dict_params[map_name_parameters[k]] in ['','0','0.0'] else bool(dict_params[map_name_parameters[k]]) + argv_new = [func(k) for k in keys if has_value(k)!=False for func in [get_key, get_value]] + argv_new.insert(0, argv[0]) + + dict_params['exp_name'] = run + dict_params['argv'] = argv_new + func_run(**dict_params) + + with open(done_file, 'w') as f: + f.write(' '.join(argv) + '\n') + else: + base_map['exp_name'] = '' + base_map['argv'] = argv + func_run(**base_map) + + +def generate_heat_map(df, number_of_features, rank_method, number_of_samples, output_path): + train_data = log_scale(df, rank_method) + cm = sns.clustermap(train_data, cmap="Blues", col_cluster=False, yticklabels=True) + plt.setp(cm.ax_heatmap.yaxis.get_majorticklabels(), fontsize=150/number_of_samples, rotation=0) + cm.ax_heatmap.set_title(f"A heat-map of the significance of the top {number_of_features} discriminatory motifs") + cm.savefig(f"{output_path}.svg", format='svg', bbox_inches="tight") + plt.close() diff --git a/auxiliaries/remove_configurations.py b/auxiliaries/remove_configurations.py index 42ee43d..72c32e8 100644 --- a/auxiliaries/remove_configurations.py +++ b/auxiliaries/remove_configurations.py @@ -4,8 +4,10 @@ import sys if os.path.exists('/groups/pupko/orenavr2/'): src_dir = '/groups/pupko/orenavr2/igomeProfilingPipeline/src' -else: +elif os.path.exists('/Users/Oren/Dropbox/Projects/'): src_dir = '/Users/Oren/Dropbox/Projects/gershoni/src' +else: + src_dir = '.' sys.path.insert(0, src_dir) from auxiliaries.pipeline_auxiliaries import verify_file_is_not_empty, load_fasta_to_dict diff --git a/auxiliaries/stop_machine_aws.py b/auxiliaries/stop_machine_aws.py new file mode 100644 index 0000000..87243d7 --- /dev/null +++ b/auxiliaries/stop_machine_aws.py @@ -0,0 +1,44 @@ +import boto3 +import botocore.exceptions +import datetime +import re + + +def is_match_name_instance(names, instance): + for name in names: + if re.search(name, instance.tags[0]['Value']): + return True + return False + + +def is_stop_instance(instance, types, names): + if instance.state['Name'] == 'stopped': + return False + if not types and not names: + return True + is_type = types and instance.instance_type in types + is_name = names and is_match_name_instance(names, instance) + return is_type or is_name + + +def stop_machines(type_machine, name_machines, logger, region_name = 'us-west-2'): + logger.info('Stop machines...') + + try: + ec2 = boto3.resource('ec2', region_name=region_name) + except botocore.exceptions.ClientError as error: + logger.error(f'{datetime.datetime.now()}: Error {error} - Can\'t connect to ec2 with boto3 for stop the machines') + + list_instances = ec2.instances.all() + + types = type_machine.split(',') if type_machine else '' + names = name_machines.split(',') if name_machines else '' + for instance in list_instances: + if is_stop_instance(instance, types, names): + try: + instance.stop() + logger.info(f'Stop the instance - Id: {instance.id}, Type: {instance.instance_type}, Public IPv4: {instance.public_ip_address}, State: {instance.state}') + except botocore.exceptions.ClientError as error: + logger.error(f'{datetime.datetime.now()}: Error {error} - can\'t stop the instance {instance.id}') + else: + logger.info(f'{datetime.datetime.now()}: Skipping on instance - {instance.id} and not stop it.') diff --git a/auxiliaries/validation_files.py b/auxiliaries/validation_files.py new file mode 100644 index 0000000..d3b2adc --- /dev/null +++ b/auxiliaries/validation_files.py @@ -0,0 +1,82 @@ +from auxiliaries.pipeline_auxiliaries import load_table_to_dict +import re +import datetime + + +def is_same_samples(samples2bc_dict, barcode2samples_dict, samplename2biologicalcondition_path, barcode2samplename_path, logger): + # verify that file sample2biologicalcondition and file barcode2sample have the same samples. + samples_from_sample2bc = sorted(samples2bc_dict.keys()) + samples_from_barcode2sample = sorted(barcode2samples_dict.values()) + if not samples_from_sample2bc == samples_from_barcode2sample: + barcodes = set(samples_from_barcode2sample) + bcs = set(samples_from_sample2bc) + diff_barcodes = barcodes - bcs + diff_bcs = bcs - barcodes + logger.error(f'The files: {samplename2biologicalcondition_path} and {barcode2samplename_path} not contain the same samples!!') + if len(diff_barcodes): + logger.error(f"Barcodes has samples which doesn't appear in biological conditions: {diff_barcodes}") + if len(diff_bcs): + logger.error(f"Biological conditions has samples which doesn't appear in barcodes: {diff_bcs}") + return False + return True + + +def is_valid_data(path_file ,dict_data, logger): + # verify the structure of the file - should look like: word\tword\n + if not dict_data: + logger.info(f'There is not data in file: {path_file}') + return False + regex = "^[A-Za-z0-9_]+$" + for key, value in dict_data.items(): + match_key = re.match(regex, key) + match_value = re.match(regex, value) + if match_key is None or match_value is None: + logger.info(f'The structure of file: {path_file} is not valid') + return False + return True + + +def load_and_validate_input_table(path, error, logger): + is_valid = True + data = None + try: + data = load_table_to_dict(path, error, is_validate_json=True) + except: + logger.error(f'{datetime.datetime.now()}: Can not load the file - {path}') + is_valid = False + if is_valid: + is_valid = is_valid_data(path, data, logger) + return is_valid, data + + +def is_input_files_valid(samplename2biologicalcondition_path, barcode2samplename_path, logger): + samples2bc_valid = True + barcode2samples_valid = True + barcode2sample_data = {} + sample2bc_data = {} + + if barcode2samplename_path: + barcode2samples_valid, barcode2sample_data = load_and_validate_input_table(barcode2samplename_path, \ + 'Barcode {} belongs to more than one sample!!', logger) + + if samplename2biologicalcondition_path: + samples2bc_valid, sample2bc_data = load_and_validate_input_table(samplename2biologicalcondition_path, \ + 'Samples {} belongs to more than one bc!!', logger) + + if not samples2bc_valid or not barcode2samples_valid: + return False + if barcode2samplename_path and samplename2biologicalcondition_path: + return is_same_samples(sample2bc_data, barcode2sample_data, samplename2biologicalcondition_path, barcode2samplename_path, logger) + return True + + +if __name__ == '__main__': + import sys + from logging import basicConfig, getLogger, INFO + basicConfig(level=INFO) + logger = getLogger('main') + + barcode2samples_path = sys.argv[1] if len(sys.argv) > 1 else "" + samples2bc_path = sys.argv[2] if len(sys.argv) > 2 else "" + is_valid = is_input_files_valid(samples2bc_path, barcode2samples_path, logger) + print(f'bar2sam="{barcode2samples_path}", sam2bc="{samples2bc_path}", is valid: {is_valid}') diff --git a/aws_upload.sh b/aws_upload.sh new file mode 100644 index 0000000..49c0ae3 --- /dev/null +++ b/aws_upload.sh @@ -0,0 +1,7 @@ +#/bin/bash +$(aws ecr get-login --no-include-email --region us-west-2) +docker tag webiks/igome-profile:latest 686447933053.dkr.ecr.us-west-2.amazonaws.com/igome-profile:latest +docker push 686447933053.dkr.ecr.us-west-2.amazonaws.com/igome-profile:latest + +docker tag webiks/igome-profile-worker:latest 686447933053.dkr.ecr.us-west-2.amazonaws.com/igome-profile-worker:latest +docker push 686447933053.dkr.ecr.us-west-2.amazonaws.com/igome-profile-worker:latest diff --git a/build_docker.sh b/build_docker.sh new file mode 100644 index 0000000..be553dc --- /dev/null +++ b/build_docker.sh @@ -0,0 +1,3 @@ +#!/bin/bash +docker build . -t webiks/igome-profile:latest +docker build . -f DockerfileWorker -t webiks/igome-profile-worker:latest diff --git a/entrypoint.sh b/entrypoint.sh new file mode 100644 index 0000000..cf9c7fc --- /dev/null +++ b/entrypoint.sh @@ -0,0 +1,3 @@ +#!/bin/bash +source .venv/bin/activate +python $APP_FILE "$@" diff --git a/global_params.py b/global_params.py index 7b34a8e..4926e3a 100644 --- a/global_params.py +++ b/global_params.py @@ -1,9 +1,18 @@ import os +is_run_on_cluster = True if os.path.exists('/groups/pupko/orenavr2/'): src_dir = '/groups/pupko/orenavr2/igomeProfilingPipeline/src' -else: +elif os.path.exists('/Users/Oren/Dropbox/Projects/'): src_dir = '/Users/Oren/Dropbox/Projects/gershoni/src' +else: + src_dir = '.' + is_run_on_cluster = False + +run_local_in_parallel_mode = True +run_using_celery = True + +local_command_prefix = "bash" # modules to load python = "python/python-anaconda3.6.5" @@ -11,7 +20,7 @@ mafft = "mafft/7.123" # external scripts -pssm_score_peptide_script = "/groups/pupko/orenavr2/igomeProfilingPipeline/src/PSSM_score_Peptide/PSSM_score_Peptide" +pssm_score_peptide_script = "./PSSM_score_Peptide/PSSM_score_Peptide" qsub_script = "/bioseq/bioSequence_scripts_and_constants/q_submitter_power.py" biggest_cluster = 100 diff --git a/hits_cpp/cxxopts.hpp b/hits_cpp/cxxopts.hpp new file mode 100644 index 0000000..95434a7 --- /dev/null +++ b/hits_cpp/cxxopts.hpp @@ -0,0 +1,2104 @@ +/* + +Copyright (c) 2014, 2015, 2016, 2017 Jarryd Beck + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + +*/ + +#ifndef CXXOPTS_HPP_INCLUDED +#define CXXOPTS_HPP_INCLUDED + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef __cpp_lib_optional +#include +#define CXXOPTS_HAS_OPTIONAL +#endif + +#ifndef CXXOPTS_VECTOR_DELIMITER +#define CXXOPTS_VECTOR_DELIMITER ',' +#endif + +#define CXXOPTS__VERSION_MAJOR 2 +#define CXXOPTS__VERSION_MINOR 2 +#define CXXOPTS__VERSION_PATCH 0 + +namespace cxxopts +{ + static constexpr struct { + uint8_t major, minor, patch; + } version = { + CXXOPTS__VERSION_MAJOR, + CXXOPTS__VERSION_MINOR, + CXXOPTS__VERSION_PATCH + }; +} + +//when we ask cxxopts to use Unicode, help strings are processed using ICU, +//which results in the correct lengths being computed for strings when they +//are formatted for the help output +//it is necessary to make sure that can be found by the +//compiler, and that icu-uc is linked in to the binary. + +#ifdef CXXOPTS_USE_UNICODE +#include + +namespace cxxopts +{ + typedef icu::UnicodeString String; + + inline + String + toLocalString(std::string s) + { + return icu::UnicodeString::fromUTF8(std::move(s)); + } + + class UnicodeStringIterator : public + std::iterator + { + public: + + UnicodeStringIterator(const icu::UnicodeString* string, int32_t pos) + : s(string) + , i(pos) + { + } + + value_type + operator*() const + { + return s->char32At(i); + } + + bool + operator==(const UnicodeStringIterator& rhs) const + { + return s == rhs.s && i == rhs.i; + } + + bool + operator!=(const UnicodeStringIterator& rhs) const + { + return !(*this == rhs); + } + + UnicodeStringIterator& + operator++() + { + ++i; + return *this; + } + + UnicodeStringIterator + operator+(int32_t v) + { + return UnicodeStringIterator(s, i + v); + } + + private: + const icu::UnicodeString* s; + int32_t i; + }; + + inline + String& + stringAppend(String&s, String a) + { + return s.append(std::move(a)); + } + + inline + String& + stringAppend(String& s, int n, UChar32 c) + { + for (int i = 0; i != n; ++i) + { + s.append(c); + } + + return s; + } + + template + String& + stringAppend(String& s, Iterator begin, Iterator end) + { + while (begin != end) + { + s.append(*begin); + ++begin; + } + + return s; + } + + inline + size_t + stringLength(const String& s) + { + return s.length(); + } + + inline + std::string + toUTF8String(const String& s) + { + std::string result; + s.toUTF8String(result); + + return result; + } + + inline + bool + empty(const String& s) + { + return s.isEmpty(); + } +} + +namespace std +{ + inline + cxxopts::UnicodeStringIterator + begin(const icu::UnicodeString& s) + { + return cxxopts::UnicodeStringIterator(&s, 0); + } + + inline + cxxopts::UnicodeStringIterator + end(const icu::UnicodeString& s) + { + return cxxopts::UnicodeStringIterator(&s, s.length()); + } +} + +//ifdef CXXOPTS_USE_UNICODE +#else + +namespace cxxopts +{ + typedef std::string String; + + template + T + toLocalString(T&& t) + { + return std::forward(t); + } + + inline + size_t + stringLength(const String& s) + { + return s.length(); + } + + inline + String& + stringAppend(String&s, String a) + { + return s.append(std::move(a)); + } + + inline + String& + stringAppend(String& s, size_t n, char c) + { + return s.append(n, c); + } + + template + String& + stringAppend(String& s, Iterator begin, Iterator end) + { + return s.append(begin, end); + } + + template + std::string + toUTF8String(T&& t) + { + return std::forward(t); + } + + inline + bool + empty(const std::string& s) + { + return s.empty(); + } +} + +//ifdef CXXOPTS_USE_UNICODE +#endif + +namespace cxxopts +{ + namespace + { +#ifdef _WIN32 + const std::string LQUOTE("\'"); + const std::string RQUOTE("\'"); +#else + const std::string LQUOTE("‘"); + const std::string RQUOTE("’"); +#endif + } + + class Value : public std::enable_shared_from_this + { + public: + + virtual ~Value() = default; + + virtual + std::shared_ptr + clone() const = 0; + + virtual void + parse(const std::string& text) const = 0; + + virtual void + parse() const = 0; + + virtual bool + has_default() const = 0; + + virtual bool + is_container() const = 0; + + virtual bool + has_implicit() const = 0; + + virtual std::string + get_default_value() const = 0; + + virtual std::string + get_implicit_value() const = 0; + + virtual std::shared_ptr + default_value(const std::string& value) = 0; + + virtual std::shared_ptr + implicit_value(const std::string& value) = 0; + + virtual std::shared_ptr + no_implicit_value() = 0; + + virtual bool + is_boolean() const = 0; + }; + + class OptionException : public std::exception + { + public: + OptionException(const std::string& message) + : m_message(message) + { + } + + virtual const char* + what() const noexcept + { + return m_message.c_str(); + } + + private: + std::string m_message; + }; + + class OptionSpecException : public OptionException + { + public: + + OptionSpecException(const std::string& message) + : OptionException(message) + { + } + }; + + class OptionParseException : public OptionException + { + public: + OptionParseException(const std::string& message) + : OptionException(message) + { + } + }; + + class option_exists_error : public OptionSpecException + { + public: + option_exists_error(const std::string& option) + : OptionSpecException("Option " + LQUOTE + option + RQUOTE + " already exists") + { + } + }; + + class invalid_option_format_error : public OptionSpecException + { + public: + invalid_option_format_error(const std::string& format) + : OptionSpecException("Invalid option format " + LQUOTE + format + RQUOTE) + { + } + }; + + class option_syntax_exception : public OptionParseException { + public: + option_syntax_exception(const std::string& text) + : OptionParseException("Argument " + LQUOTE + text + RQUOTE + + " starts with a - but has incorrect syntax") + { + } + }; + + class option_not_exists_exception : public OptionParseException + { + public: + option_not_exists_exception(const std::string& option) + : OptionParseException("Option " + LQUOTE + option + RQUOTE + " does not exist") + { + } + }; + + class missing_argument_exception : public OptionParseException + { + public: + missing_argument_exception(const std::string& option) + : OptionParseException( + "Option " + LQUOTE + option + RQUOTE + " is missing an argument" + ) + { + } + }; + + class option_requires_argument_exception : public OptionParseException + { + public: + option_requires_argument_exception(const std::string& option) + : OptionParseException( + "Option " + LQUOTE + option + RQUOTE + " requires an argument" + ) + { + } + }; + + class option_not_has_argument_exception : public OptionParseException + { + public: + option_not_has_argument_exception + ( + const std::string& option, + const std::string& arg + ) + : OptionParseException( + "Option " + LQUOTE + option + RQUOTE + + " does not take an argument, but argument " + + LQUOTE + arg + RQUOTE + " given" + ) + { + } + }; + + class option_not_present_exception : public OptionParseException + { + public: + option_not_present_exception(const std::string& option) + : OptionParseException("Option " + LQUOTE + option + RQUOTE + " not present") + { + } + }; + + class argument_incorrect_type : public OptionParseException + { + public: + argument_incorrect_type + ( + const std::string& arg + ) + : OptionParseException( + "Argument " + LQUOTE + arg + RQUOTE + " failed to parse" + ) + { + } + }; + + class option_required_exception : public OptionParseException + { + public: + option_required_exception(const std::string& option) + : OptionParseException( + "Option " + LQUOTE + option + RQUOTE + " is required but not present" + ) + { + } + }; + + namespace values + { + namespace + { + std::basic_regex integer_pattern + ("(-)?(0x)?([0-9a-zA-Z]+)|((0x)?0)"); + std::basic_regex truthy_pattern + ("(t|T)(rue)?|1"); + std::basic_regex falsy_pattern + ("(f|F)(alse)?|0"); + } + + namespace detail + { + template + struct SignedCheck; + + template + struct SignedCheck + { + template + void + operator()(bool negative, U u, const std::string& text) + { + if (negative) + { + if (u > static_cast((std::numeric_limits::min)())) + { + throw argument_incorrect_type(text); + } + } + else + { + if (u > static_cast((std::numeric_limits::max)())) + { + throw argument_incorrect_type(text); + } + } + } + }; + + template + struct SignedCheck + { + template + void + operator()(bool, U, const std::string&) {} + }; + + template + void + check_signed_range(bool negative, U value, const std::string& text) + { + SignedCheck::is_signed>()(negative, value, text); + } + } + + template + R + checked_negate(T&& t, const std::string&, std::true_type) + { + // if we got to here, then `t` is a positive number that fits into + // `R`. So to avoid MSVC C4146, we first cast it to `R`. + // See https://github.com/jarro2783/cxxopts/issues/62 for more details. + return -static_cast(t-1)-1; + } + + template + T + checked_negate(T&&, const std::string& text, std::false_type) + { + throw argument_incorrect_type(text); + } + + template + void + integer_parser(const std::string& text, T& value) + { + std::smatch match; + std::regex_match(text, match, integer_pattern); + + if (match.length() == 0) + { + throw argument_incorrect_type(text); + } + + if (match.length(4) > 0) + { + value = 0; + return; + } + + using US = typename std::make_unsigned::type; + + constexpr bool is_signed = std::numeric_limits::is_signed; + const bool negative = match.length(1) > 0; + const uint8_t base = match.length(2) > 0 ? 16 : 10; + + auto value_match = match[3]; + + US result = 0; + + for (auto iter = value_match.first; iter != value_match.second; ++iter) + { + US digit = 0; + + if (*iter >= '0' && *iter <= '9') + { + digit = static_cast(*iter - '0'); + } + else if (base == 16 && *iter >= 'a' && *iter <= 'f') + { + digit = static_cast(*iter - 'a' + 10); + } + else if (base == 16 && *iter >= 'A' && *iter <= 'F') + { + digit = static_cast(*iter - 'A' + 10); + } + else + { + throw argument_incorrect_type(text); + } + + US next = result * base + digit; + if (result > next) + { + throw argument_incorrect_type(text); + } + + result = next; + } + + detail::check_signed_range(negative, result, text); + + if (negative) + { + value = checked_negate(result, + text, + std::integral_constant()); + } + else + { + value = static_cast(result); + } + } + + template + void stringstream_parser(const std::string& text, T& value) + { + std::stringstream in(text); + in >> value; + if (!in) { + throw argument_incorrect_type(text); + } + } + + inline + void + parse_value(const std::string& text, uint8_t& value) + { + integer_parser(text, value); + } + + inline + void + parse_value(const std::string& text, int8_t& value) + { + integer_parser(text, value); + } + + inline + void + parse_value(const std::string& text, uint16_t& value) + { + integer_parser(text, value); + } + + inline + void + parse_value(const std::string& text, int16_t& value) + { + integer_parser(text, value); + } + + inline + void + parse_value(const std::string& text, uint32_t& value) + { + integer_parser(text, value); + } + + inline + void + parse_value(const std::string& text, int32_t& value) + { + integer_parser(text, value); + } + + inline + void + parse_value(const std::string& text, uint64_t& value) + { + integer_parser(text, value); + } + + inline + void + parse_value(const std::string& text, int64_t& value) + { + integer_parser(text, value); + } + + inline + void + parse_value(const std::string& text, bool& value) + { + std::smatch result; + std::regex_match(text, result, truthy_pattern); + + if (!result.empty()) + { + value = true; + return; + } + + std::regex_match(text, result, falsy_pattern); + if (!result.empty()) + { + value = false; + return; + } + + throw argument_incorrect_type(text); + } + + inline + void + parse_value(const std::string& text, std::string& value) + { + value = text; + } + + // The fallback parser. It uses the stringstream parser to parse all types + // that have not been overloaded explicitly. It has to be placed in the + // source code before all other more specialized templates. + template + void + parse_value(const std::string& text, T& value) { + stringstream_parser(text, value); + } + + template + void + parse_value(const std::string& text, std::vector& value) + { + std::stringstream in(text); + std::string token; + while(in.eof() == false && std::getline(in, token, CXXOPTS_VECTOR_DELIMITER)) { + T v; + parse_value(token, v); + value.emplace_back(std::move(v)); + } + } + +#ifdef CXXOPTS_HAS_OPTIONAL + template + void + parse_value(const std::string& text, std::optional& value) + { + T result; + parse_value(text, result); + value = std::move(result); + } +#endif + + template + struct type_is_container + { + static constexpr bool value = false; + }; + + template + struct type_is_container> + { + static constexpr bool value = true; + }; + + template + class abstract_value : public Value + { + using Self = abstract_value; + + public: + abstract_value() + : m_result(std::make_shared()) + , m_store(m_result.get()) + { + } + + abstract_value(T* t) + : m_store(t) + { + } + + virtual ~abstract_value() = default; + + abstract_value(const abstract_value& rhs) + { + if (rhs.m_result) + { + m_result = std::make_shared(); + m_store = m_result.get(); + } + else + { + m_store = rhs.m_store; + } + + m_default = rhs.m_default; + m_implicit = rhs.m_implicit; + m_default_value = rhs.m_default_value; + m_implicit_value = rhs.m_implicit_value; + } + + void + parse(const std::string& text) const + { + parse_value(text, *m_store); + } + + bool + is_container() const + { + return type_is_container::value; + } + + void + parse() const + { + parse_value(m_default_value, *m_store); + } + + bool + has_default() const + { + return m_default; + } + + bool + has_implicit() const + { + return m_implicit; + } + + std::shared_ptr + default_value(const std::string& value) + { + m_default = true; + m_default_value = value; + return shared_from_this(); + } + + std::shared_ptr + implicit_value(const std::string& value) + { + m_implicit = true; + m_implicit_value = value; + return shared_from_this(); + } + + std::shared_ptr + no_implicit_value() + { + m_implicit = false; + return shared_from_this(); + } + + std::string + get_default_value() const + { + return m_default_value; + } + + std::string + get_implicit_value() const + { + return m_implicit_value; + } + + bool + is_boolean() const + { + return std::is_same::value; + } + + const T& + get() const + { + if (m_store == nullptr) + { + return *m_result; + } + else + { + return *m_store; + } + } + + protected: + std::shared_ptr m_result; + T* m_store; + + bool m_default = false; + bool m_implicit = false; + + std::string m_default_value; + std::string m_implicit_value; + }; + + template + class standard_value : public abstract_value + { + public: + using abstract_value::abstract_value; + + std::shared_ptr + clone() const + { + return std::make_shared>(*this); + } + }; + + template <> + class standard_value : public abstract_value + { + public: + ~standard_value() = default; + + standard_value() + { + set_default_and_implicit(); + } + + standard_value(bool* b) + : abstract_value(b) + { + set_default_and_implicit(); + } + + std::shared_ptr + clone() const + { + return std::make_shared>(*this); + } + + private: + + void + set_default_and_implicit() + { + m_default = true; + m_default_value = "false"; + m_implicit = true; + m_implicit_value = "true"; + } + }; + } + + template + std::shared_ptr + value() + { + return std::make_shared>(); + } + + template + std::shared_ptr + value(T& t) + { + return std::make_shared>(&t); + } + + class OptionAdder; + + class OptionDetails + { + public: + OptionDetails + ( + const std::string& short_, + const std::string& long_, + const String& desc, + std::shared_ptr val + ) + : m_short(short_) + , m_long(long_) + , m_desc(desc) + , m_value(val) + , m_count(0) + { + } + + OptionDetails(const OptionDetails& rhs) + : m_desc(rhs.m_desc) + , m_count(rhs.m_count) + { + m_value = rhs.m_value->clone(); + } + + OptionDetails(OptionDetails&& rhs) = default; + + const String& + description() const + { + return m_desc; + } + + const Value& value() const { + return *m_value; + } + + std::shared_ptr + make_storage() const + { + return m_value->clone(); + } + + const std::string& + short_name() const + { + return m_short; + } + + const std::string& + long_name() const + { + return m_long; + } + + private: + std::string m_short; + std::string m_long; + String m_desc; + std::shared_ptr m_value; + int m_count; + }; + + struct HelpOptionDetails + { + std::string s; + std::string l; + String desc; + bool has_default; + std::string default_value; + bool has_implicit; + std::string implicit_value; + std::string arg_help; + bool is_container; + bool is_boolean; + }; + + struct HelpGroupDetails + { + std::string name; + std::string description; + std::vector options; + }; + + class OptionValue + { + public: + void + parse + ( + std::shared_ptr details, + const std::string& text + ) + { + ensure_value(details); + ++m_count; + m_value->parse(text); + } + + void + parse_default(std::shared_ptr details) + { + ensure_value(details); + m_value->parse(); + } + + size_t + count() const + { + return m_count; + } + + template + const T& + as() const + { + if (m_value == nullptr) { + throw std::domain_error("No value"); + } + +#ifdef CXXOPTS_NO_RTTI + return static_cast&>(*m_value).get(); +#else + return dynamic_cast&>(*m_value).get(); +#endif + } + + private: + void + ensure_value(std::shared_ptr details) + { + if (m_value == nullptr) + { + m_value = details->make_storage(); + } + } + + std::shared_ptr m_value; + size_t m_count = 0; + }; + + class KeyValue + { + public: + KeyValue(std::string key_, std::string value_) + : m_key(std::move(key_)) + , m_value(std::move(value_)) + { + } + + const + std::string& + key() const + { + return m_key; + } + + const + std::string& + value() const + { + return m_value; + } + + template + T + as() const + { + T result; + values::parse_value(m_value, result); + return result; + } + + private: + std::string m_key; + std::string m_value; + }; + + class ParseResult + { + public: + + ParseResult( + const std::shared_ptr< + std::unordered_map> + >, + std::vector, + bool allow_unrecognised, + int&, char**&); + + size_t + count(const std::string& o) const + { + auto iter = m_options->find(o); + if (iter == m_options->end()) + { + return 0; + } + + auto riter = m_results.find(iter->second); + + return riter->second.count(); + } + + const OptionValue& + operator[](const std::string& option) const + { + auto iter = m_options->find(option); + + if (iter == m_options->end()) + { + throw option_not_present_exception(option); + } + + auto riter = m_results.find(iter->second); + + return riter->second; + } + + const std::vector& + arguments() const + { + return m_sequential; + } + + private: + + void + parse(int& argc, char**& argv); + + void + add_to_option(const std::string& option, const std::string& arg); + + bool + consume_positional(std::string a); + + void + parse_option + ( + std::shared_ptr value, + const std::string& name, + const std::string& arg = "" + ); + + void + parse_default(std::shared_ptr details); + + void + checked_parse_arg + ( + int argc, + char* argv[], + int& current, + std::shared_ptr value, + const std::string& name + ); + + const std::shared_ptr< + std::unordered_map> + > m_options; + std::vector m_positional; + std::vector::iterator m_next_positional; + std::unordered_set m_positional_set; + std::unordered_map, OptionValue> m_results; + + bool m_allow_unrecognised; + + std::vector m_sequential; + }; + + class Options + { + typedef std::unordered_map> + OptionMap; + public: + + Options(std::string program, std::string help_string = "") + : m_program(std::move(program)) + , m_help_string(toLocalString(std::move(help_string))) + , m_custom_help("[OPTION...]") + , m_positional_help("positional parameters") + , m_show_positional(false) + , m_allow_unrecognised(false) + , m_options(std::make_shared()) + , m_next_positional(m_positional.end()) + { + } + + Options& + positional_help(std::string help_text) + { + m_positional_help = std::move(help_text); + return *this; + } + + Options& + custom_help(std::string help_text) + { + m_custom_help = std::move(help_text); + return *this; + } + + Options& + show_positional_help() + { + m_show_positional = true; + return *this; + } + + Options& + allow_unrecognised_options() + { + m_allow_unrecognised = true; + return *this; + } + + ParseResult + parse(int& argc, char**& argv); + + OptionAdder + add_options(std::string group = ""); + + void + add_option + ( + const std::string& group, + const std::string& s, + const std::string& l, + std::string desc, + std::shared_ptr value, + std::string arg_help + ); + + //parse positional arguments into the given option + void + parse_positional(std::string option); + + void + parse_positional(std::vector options); + + void + parse_positional(std::initializer_list options); + + template + void + parse_positional(Iterator begin, Iterator end) { + parse_positional(std::vector{begin, end}); + } + + std::string + help(const std::vector& groups = {}) const; + + const std::vector + groups() const; + + const HelpGroupDetails& + group_help(const std::string& group) const; + + private: + + void + add_one_option + ( + const std::string& option, + std::shared_ptr details + ); + + String + help_one_group(const std::string& group) const; + + void + generate_group_help + ( + String& result, + const std::vector& groups + ) const; + + void + generate_all_groups_help(String& result) const; + + std::string m_program; + String m_help_string; + std::string m_custom_help; + std::string m_positional_help; + bool m_show_positional; + bool m_allow_unrecognised; + + std::shared_ptr m_options; + std::vector m_positional; + std::vector::iterator m_next_positional; + std::unordered_set m_positional_set; + + //mapping from groups to help options + std::map m_help; + }; + + class OptionAdder + { + public: + + OptionAdder(Options& options, std::string group) + : m_options(options), m_group(std::move(group)) + { + } + + OptionAdder& + operator() + ( + const std::string& opts, + const std::string& desc, + std::shared_ptr value + = ::cxxopts::value(), + std::string arg_help = "" + ); + + private: + Options& m_options; + std::string m_group; + }; + + namespace + { + constexpr int OPTION_LONGEST = 30; + constexpr int OPTION_DESC_GAP = 2; + + std::basic_regex option_matcher + ("--([[:alnum:]][-_[:alnum:]]+)(=(.*))?|-([[:alnum:]]+)"); + + std::basic_regex option_specifier + ("(([[:alnum:]]),)?[ ]*([[:alnum:]][-_[:alnum:]]*)?"); + + String + format_option + ( + const HelpOptionDetails& o + ) + { + auto& s = o.s; + auto& l = o.l; + + String result = " "; + + if (s.size() > 0) + { + result += "-" + toLocalString(s) + ","; + } + else + { + result += " "; + } + + if (l.size() > 0) + { + result += " --" + toLocalString(l); + } + + auto arg = o.arg_help.size() > 0 ? toLocalString(o.arg_help) : "arg"; + + if (!o.is_boolean) + { + if (o.has_implicit) + { + result += " [=" + arg + "(=" + toLocalString(o.implicit_value) + ")]"; + } + else + { + result += " " + arg; + } + } + + return result; + } + + String + format_description + ( + const HelpOptionDetails& o, + size_t start, + size_t width + ) + { + auto desc = o.desc; + + if (o.has_default && (!o.is_boolean || o.default_value != "false")) + { + desc += toLocalString(" (default: " + o.default_value + ")"); + } + + String result; + + auto current = std::begin(desc); + auto startLine = current; + auto lastSpace = current; + + auto size = size_t{}; + + while (current != std::end(desc)) + { + if (*current == ' ') + { + lastSpace = current; + } + + if (*current == '\n') + { + startLine = current + 1; + lastSpace = startLine; + } + else if (size > width) + { + if (lastSpace == startLine) + { + stringAppend(result, startLine, current + 1); + stringAppend(result, "\n"); + stringAppend(result, start, ' '); + startLine = current + 1; + lastSpace = startLine; + } + else + { + stringAppend(result, startLine, lastSpace); + stringAppend(result, "\n"); + stringAppend(result, start, ' '); + startLine = lastSpace + 1; + } + size = 0; + } + else + { + ++size; + } + + ++current; + } + + //append whatever is left + stringAppend(result, startLine, current); + + return result; + } + } + +inline +ParseResult::ParseResult +( + const std::shared_ptr< + std::unordered_map> + > options, + std::vector positional, + bool allow_unrecognised, + int& argc, char**& argv +) +: m_options(options) +, m_positional(std::move(positional)) +, m_next_positional(m_positional.begin()) +, m_allow_unrecognised(allow_unrecognised) +{ + parse(argc, argv); +} + +inline +OptionAdder +Options::add_options(std::string group) +{ + return OptionAdder(*this, std::move(group)); +} + +inline +OptionAdder& +OptionAdder::operator() +( + const std::string& opts, + const std::string& desc, + std::shared_ptr value, + std::string arg_help +) +{ + std::match_results result; + std::regex_match(opts.c_str(), result, option_specifier); + + if (result.empty()) + { + throw invalid_option_format_error(opts); + } + + const auto& short_match = result[2]; + const auto& long_match = result[3]; + + if (!short_match.length() && !long_match.length()) + { + throw invalid_option_format_error(opts); + } else if (long_match.length() == 1 && short_match.length()) + { + throw invalid_option_format_error(opts); + } + + auto option_names = [] + ( + const std::sub_match& short_, + const std::sub_match& long_ + ) + { + if (long_.length() == 1) + { + return std::make_tuple(long_.str(), short_.str()); + } + else + { + return std::make_tuple(short_.str(), long_.str()); + } + }(short_match, long_match); + + m_options.add_option + ( + m_group, + std::get<0>(option_names), + std::get<1>(option_names), + desc, + value, + std::move(arg_help) + ); + + return *this; +} + +inline +void +ParseResult::parse_default(std::shared_ptr details) +{ + m_results[details].parse_default(details); +} + +inline +void +ParseResult::parse_option +( + std::shared_ptr value, + const std::string& /*name*/, + const std::string& arg +) +{ + auto& result = m_results[value]; + result.parse(value, arg); + + m_sequential.emplace_back(value->long_name(), arg); +} + +inline +void +ParseResult::checked_parse_arg +( + int argc, + char* argv[], + int& current, + std::shared_ptr value, + const std::string& name +) +{ + if (current + 1 >= argc) + { + if (value->value().has_implicit()) + { + parse_option(value, name, value->value().get_implicit_value()); + } + else + { + throw missing_argument_exception(name); + } + } + else + { + if (value->value().has_implicit()) + { + parse_option(value, name, value->value().get_implicit_value()); + } + else + { + parse_option(value, name, argv[current + 1]); + ++current; + } + } +} + +inline +void +ParseResult::add_to_option(const std::string& option, const std::string& arg) +{ + auto iter = m_options->find(option); + + if (iter == m_options->end()) + { + throw option_not_exists_exception(option); + } + + parse_option(iter->second, option, arg); +} + +inline +bool +ParseResult::consume_positional(std::string a) +{ + while (m_next_positional != m_positional.end()) + { + auto iter = m_options->find(*m_next_positional); + if (iter != m_options->end()) + { + auto& result = m_results[iter->second]; + if (!iter->second->value().is_container()) + { + if (result.count() == 0) + { + add_to_option(*m_next_positional, a); + ++m_next_positional; + return true; + } + else + { + ++m_next_positional; + continue; + } + } + else + { + add_to_option(*m_next_positional, a); + return true; + } + } + else + { + throw option_not_exists_exception(*m_next_positional); + } + } + + return false; +} + +inline +void +Options::parse_positional(std::string option) +{ + parse_positional(std::vector{std::move(option)}); +} + +inline +void +Options::parse_positional(std::vector options) +{ + m_positional = std::move(options); + m_next_positional = m_positional.begin(); + + m_positional_set.insert(m_positional.begin(), m_positional.end()); +} + +inline +void +Options::parse_positional(std::initializer_list options) +{ + parse_positional(std::vector(std::move(options))); +} + +inline +ParseResult +Options::parse(int& argc, char**& argv) +{ + ParseResult result(m_options, m_positional, m_allow_unrecognised, argc, argv); + return result; +} + +inline +void +ParseResult::parse(int& argc, char**& argv) +{ + int current = 1; + + int nextKeep = 1; + + bool consume_remaining = false; + + while (current != argc) + { + if (strcmp(argv[current], "--") == 0) + { + consume_remaining = true; + ++current; + break; + } + + std::match_results result; + std::regex_match(argv[current], result, option_matcher); + + if (result.empty()) + { + //not a flag + + // but if it starts with a `-`, then it's an error + if (argv[current][0] == '-' && argv[current][1] != '\0') { + if (!m_allow_unrecognised) { + throw option_syntax_exception(argv[current]); + } + } + + //if true is returned here then it was consumed, otherwise it is + //ignored + if (consume_positional(argv[current])) + { + } + else + { + argv[nextKeep] = argv[current]; + ++nextKeep; + } + //if we return from here then it was parsed successfully, so continue + } + else + { + //short or long option? + if (result[4].length() != 0) + { + const std::string& s = result[4]; + + for (std::size_t i = 0; i != s.size(); ++i) + { + std::string name(1, s[i]); + auto iter = m_options->find(name); + + if (iter == m_options->end()) + { + if (m_allow_unrecognised) + { + continue; + } + else + { + //error + throw option_not_exists_exception(name); + } + } + + auto value = iter->second; + + if (i + 1 == s.size()) + { + //it must be the last argument + checked_parse_arg(argc, argv, current, value, name); + } + else if (value->value().has_implicit()) + { + parse_option(value, name, value->value().get_implicit_value()); + } + else + { + //error + throw option_requires_argument_exception(name); + } + } + } + else if (result[1].length() != 0) + { + const std::string& name = result[1]; + + auto iter = m_options->find(name); + + if (iter == m_options->end()) + { + if (m_allow_unrecognised) + { + // keep unrecognised options in argument list, skip to next argument + argv[nextKeep] = argv[current]; + ++nextKeep; + ++current; + continue; + } + else + { + //error + throw option_not_exists_exception(name); + } + } + + auto opt = iter->second; + + //equals provided for long option? + if (result[2].length() != 0) + { + //parse the option given + + parse_option(opt, name, result[3]); + } + else + { + //parse the next argument + checked_parse_arg(argc, argv, current, opt, name); + } + } + + } + + ++current; + } + + for (auto& opt : *m_options) + { + auto& detail = opt.second; + auto& value = detail->value(); + + auto& store = m_results[detail]; + + if(!store.count() && value.has_default()){ + parse_default(detail); + } + } + + if (consume_remaining) + { + while (current < argc) + { + if (!consume_positional(argv[current])) { + break; + } + ++current; + } + + //adjust argv for any that couldn't be swallowed + while (current != argc) { + argv[nextKeep] = argv[current]; + ++nextKeep; + ++current; + } + } + + argc = nextKeep; + +} + +inline +void +Options::add_option +( + const std::string& group, + const std::string& s, + const std::string& l, + std::string desc, + std::shared_ptr value, + std::string arg_help +) +{ + auto stringDesc = toLocalString(std::move(desc)); + auto option = std::make_shared(s, l, stringDesc, value); + + if (s.size() > 0) + { + add_one_option(s, option); + } + + if (l.size() > 0) + { + add_one_option(l, option); + } + + //add the help details + auto& options = m_help[group]; + + options.options.emplace_back(HelpOptionDetails{s, l, stringDesc, + value->has_default(), value->get_default_value(), + value->has_implicit(), value->get_implicit_value(), + std::move(arg_help), + value->is_container(), + value->is_boolean()}); +} + +inline +void +Options::add_one_option +( + const std::string& option, + std::shared_ptr details +) +{ + auto in = m_options->emplace(option, details); + + if (!in.second) + { + throw option_exists_error(option); + } +} + +inline +String +Options::help_one_group(const std::string& g) const +{ + typedef std::vector> OptionHelp; + + auto group = m_help.find(g); + if (group == m_help.end()) + { + return ""; + } + + OptionHelp format; + + size_t longest = 0; + + String result; + + if (!g.empty()) + { + result += toLocalString(" " + g + " options:\n"); + } + + for (const auto& o : group->second.options) + { + if (m_positional_set.find(o.l) != m_positional_set.end() && + !m_show_positional) + { + continue; + } + + auto s = format_option(o); + longest = (std::max)(longest, stringLength(s)); + format.push_back(std::make_pair(s, String())); + } + + longest = (std::min)(longest, static_cast(OPTION_LONGEST)); + + //widest allowed description + auto allowed = size_t{76} - longest - OPTION_DESC_GAP; + + auto fiter = format.begin(); + for (const auto& o : group->second.options) + { + if (m_positional_set.find(o.l) != m_positional_set.end() && + !m_show_positional) + { + continue; + } + + auto d = format_description(o, longest + OPTION_DESC_GAP, allowed); + + result += fiter->first; + if (stringLength(fiter->first) > longest) + { + result += '\n'; + result += toLocalString(std::string(longest + OPTION_DESC_GAP, ' ')); + } + else + { + result += toLocalString(std::string(longest + OPTION_DESC_GAP - + stringLength(fiter->first), + ' ')); + } + result += d; + result += '\n'; + + ++fiter; + } + + return result; +} + +inline +void +Options::generate_group_help +( + String& result, + const std::vector& print_groups +) const +{ + for (size_t i = 0; i != print_groups.size(); ++i) + { + const String& group_help_text = help_one_group(print_groups[i]); + if (empty(group_help_text)) + { + continue; + } + result += group_help_text; + if (i < print_groups.size() - 1) + { + result += '\n'; + } + } +} + +inline +void +Options::generate_all_groups_help(String& result) const +{ + std::vector all_groups; + all_groups.reserve(m_help.size()); + + for (auto& group : m_help) + { + all_groups.push_back(group.first); + } + + generate_group_help(result, all_groups); +} + +inline +std::string +Options::help(const std::vector& help_groups) const +{ + String result = m_help_string + "\nUsage:\n " + + toLocalString(m_program) + " " + toLocalString(m_custom_help); + + if (m_positional.size() > 0 && m_positional_help.size() > 0) { + result += " " + toLocalString(m_positional_help); + } + + result += "\n\n"; + + if (help_groups.size() == 0) + { + generate_all_groups_help(result); + } + else + { + generate_group_help(result, help_groups); + } + + return toUTF8String(result); +} + +inline +const std::vector +Options::groups() const +{ + std::vector g; + + std::transform( + m_help.begin(), + m_help.end(), + std::back_inserter(g), + [] (const std::map::value_type& pair) + { + return pair.first; + } + ); + + return g; +} + +inline +const HelpGroupDetails& +Options::group_help(const std::string& group) const +{ + return m_help.at(group); +} + +} + +#endif //CXXOPTS_HPP_INCLUDED \ No newline at end of file diff --git a/hits_cpp/hits.cpp b/hits_cpp/hits.cpp new file mode 100644 index 0000000..e8d1d62 --- /dev/null +++ b/hits_cpp/hits.cpp @@ -0,0 +1,342 @@ +#include +#include +#include +#include +#include +#include +#include + +#include "cxxopts.hpp" +#include "types.hpp" +#include "trim.hpp" +#include "meme.hpp" +#include "memes.hpp" +#include "shufflesGenerator.hpp" + +Memes loadMemes(string memePath, int limit, bool verbose); +void loadCutoffs(string cutoffsPath, Memes& memes, int limit, bool verbose); +SequencesMap loadSequences(string faaPath, int& numSequences, SequencesRpmMap& seeuncesRpm, bool useRpmFaaScanning, bool verbose); + +// TODO support Repeats_ +// TODO move createShuffles, isHit and getHits? +MemeShufflesMap createShuffles(Memes& memes, int shuffles) { + MemeShufflesMap memesShuffles; + + if (shuffles) { + // Get keys (before mutation) + vector keys; + auto memesIter = memes.getMemes().begin(); + auto memesEnd = memes.getMemes().end(); + while (memesIter != memesEnd) { + keys.push_back(memesIter->first); + memesIter++; + } + + // Add shuffles + auto keysIter = keys.begin(); + auto keysEnd = keys.end(); + while (keysIter != keysEnd) { + auto shuffler = getShuffler(memes.getMemes()[*keysIter], shuffles); + while (shuffler.next()) { + memesShuffles[*keysIter].push_back(shuffler.generate()); + } + keysIter++; + } + } + + return memesShuffles; +} + +bool isHit(Meme& meme, AlphabetMap& alphabet, string seqType, string& seq, bool verbose) { + auto iter = meme.getCuttofs().find(seqType); + if (iter == meme.getCuttofs().end()) { + return false; + } + auto cutoffValue = iter->second; + auto seqLen = seq.length(); + auto memeLen = meme.getRows().size(); + + int start = -memeLen + 1; + int end = min(memeLen, seqLen); + double totalScore = 0; + double score = 0; + char c; + for (int i = start; i < end; i++) { + totalScore = 1; + for (int j = 0; j < memeLen; j++) { + c = ((i + j < 0) || (i + j) >= seqLen) ? '-' : seq[i + j]; + score = meme.getRows()[j][alphabet[c]]; + // TODO check if equal to -inf, set total_score and break + totalScore *= score; + } + if (log(totalScore) > cutoffValue) { + return true; + } + } + return false; +} + +void memeHits(Meme& meme, AlphabetMap& alphabet, SequencesMap& sequences, double& hits, + int printInterval, bool isOutputSequences, SequencesRpmMap& sequncesRpm, bool isUseRpmFaa, bool verbose) { + auto sequencesTypesIter = sequences.begin(); + auto sequencesTypesEnd = sequences.end(); + int counter = 0; + + while (sequencesTypesIter != sequencesTypesEnd) { + auto sequencesIter = sequencesTypesIter->second->begin(); + auto sequencesEnd = sequencesTypesIter->second->end(); + while (sequencesIter != sequencesEnd) { + if (counter && (counter % printInterval) == 0) { + cout << "seq: " << counter << ", overall hits: " << hits << endl; + } + counter++; + if (isHit(meme, alphabet, sequencesTypesIter->first, *sequencesIter, verbose)) { + if (isUseRpmFaa){ + meme.addHitSequence(*sequencesIter, isOutputSequences, sequncesRpm.find(*sequencesIter)->second); + hits += sequncesRpm.find(*sequencesIter)->second; + } else { + meme.addHitSequence(*sequencesIter, isOutputSequences, 1); + hits += 1; + } + } + sequencesIter++; + } + sequencesTypesIter++; + } + cout << "meme hits: " << meme.getHitCount() << endl; +} + +void writeSequenceHits(SequencesCount& hitSequences, SequencesRpmMap& sequncesRpm, string motif, string sequenceHitMotifPath){ + ofstream fileSequenceHit; + fileSequenceHit.open(sequenceHitMotifPath, std::ios_base::app); + fileSequenceHit << "MOTIF " << motif << endl; + auto sequencesTypesIter = hitSequences.begin(); + auto sequencesTypesEnd = hitSequences.end(); + while (sequencesTypesIter != sequencesTypesEnd){ + fileSequenceHit << sequencesTypesIter->first << " " << sequncesRpm.find(sequencesTypesIter->first)->second << endl; + sequencesTypesIter++; + } + fileSequenceHit.close(); +} + +int getHits(Memes& memes, SequencesMap& sequences, MemeShufflesMap& shuffles, bool isOutputSequences, + SequencesRpmMap& sequncesRpm, bool useRpmFaaScanning, bool verbose) { + if (verbose) { + cout << "GET HITS" << endl; + } + auto alphabet = memes.getAlphabet(); + auto memesIter = memes.getMemes().begin(); + auto memesEnd = memes.getMemes().end(); + double hits = 0.0; + double shuffleHits = 0.0; + int counter = 0; + int printInterval = 100000; + if (verbose) { + printInterval = 10000; + } + + while (memesIter != memesEnd) { + if (verbose) { + cout << "Calculating hits for " << memesIter->first << endl; + } + memeHits(memesIter->second, alphabet, sequences, hits, + printInterval, isOutputSequences, sequncesRpm, useRpmFaaScanning, verbose); + auto memeShuffles = &shuffles[memesIter->first]; + if (memeShuffles->size()) { + counter = 0; + auto shufflesIter = memeShuffles->begin(); + auto shufflesEnd = memeShuffles->end(); + while (shufflesIter != shufflesEnd) { + if (verbose) { + cout << "Calculating hits for shuffle " << ++counter << endl; + } + memeHits(*shufflesIter, alphabet, sequences, shuffleHits, + printInterval, isOutputSequences, sequncesRpm, useRpmFaaScanning, verbose); + shufflesIter++; + } + } + + memesIter++; + } + cout << "total hits: " << hits << endl; + return hits; +} + +MemeRatingMap getRatings(Memes& memes, MemeShufflesMap& shuffles, bool verbose, float shufflesPercent) { + MemeRatingMap ratings; + + auto memeIter = shuffles.begin(); + auto memeEnd = shuffles.end(); + + while (memeIter != memeEnd) { + auto hits = memes.getMemes()[memeIter->first].getHitCount(); + + auto shuffleIter = memeIter->second.begin(); + auto shuffleEnd = memeIter->second.end(); + + int add = memeIter->second.size(); + if (shufflesPercent != 0.0) { + add++; + } + int shuffleArray[add]; + int count = 0; + int max = 0; + //create list of the suffle hits + while (shuffleIter != shuffleEnd) { + shuffleArray[count] = shuffleIter->getHitCount(); + if (shuffleIter->getHitCount() > max){ + max=shuffleIter->getHitCount(); + } + shuffleIter++; + count++; + } + if (add != memeIter->second.size()) { + shuffleArray[memeIter->second.size()] = (int)((float)max * shufflesPercent) + max; + } + //sort the list + int n = sizeof(shuffleArray) / sizeof(shuffleArray[0]); + sort(shuffleArray, shuffleArray + n); + + int shuffleHitStart = shuffleArray[0]; + int shuffleHitEnd = shuffleArray[n-1]; + float score; + if (hits == 0) { + score = 0.0; + } + else if (hits > shuffleHitEnd) { + score = 1.0; + } + else if (hits < shuffleHitStart) { + score = 0.0; + }else{ + for (int place = 0; place < n ;place++) { + if (hits <=shuffleArray[place]) { + float min_1 = (float)shuffleArray[place - 1]; + float max_1 = (float)shuffleArray[place]; + float z_maen = ((float)hits - min_1) / (max_1 - min_1); + float p = (float)place / (float)n; + score = (z_maen / (float)n) + p; + break; + } + } + } + if (verbose) { + cout << "Motif " << memeIter->first << ", Total shuffles: " << memeIter->second.size() << + ", Score: " << score << endl; + } + ratings[memeIter->first] = score; + memeIter++; + } + + return ratings; +} + +double getRpmFactor(int numSequences) { + double factor; + if (numSequences != 0) { + factor = double(1000000) / double(numSequences); + } + return factor; +} + +void factorHits(Memes& memes, MemeShufflesMap& shuffles, double factor) { + auto memesIter = memes.getMemes().begin(); + auto memesEnd = memes.getMemes().end(); + while (memesIter != memesEnd) { + memesIter->second.factorHitCount(factor); + auto memeShuffles = &shuffles[memesIter->first]; + if (memeShuffles->size()) { + auto shufflesIter = memeShuffles->begin(); + auto shufflesEnd = memeShuffles->end(); + while (shufflesIter != shufflesEnd) { + (*shufflesIter).factorHitCount(factor); + shufflesIter++; + } + } + memesIter++; + } +} + +void writeResults(Memes& memes, MemeRatingMap& ratings, MemeShufflesMap& shuffles, string& outputPath, SequencesRpmMap& sequncesRpm, bool isOutputSequences, \ + string& sequenceHitMotifPath, bool useRPM, bool useRpmFaaScanning, bool verbose, int shufflesDigits) { + auto memesIter = memes.getMemes().begin(); + auto memesEnd = memes.getMemes().end(); + auto ratingEnd = ratings.end(); + ofstream file(outputPath); + + while (memesIter != memesEnd) { + file << "MOTIF " << memesIter->second.getMotif() << endl; + file << "HITS " << memesIter->second.getHitCount() << endl; + auto ratingIter = ratings.find(memesIter->first); + if (ratingIter != ratingEnd) { + file << "SHUFFLES " << shuffles[memesIter->first].size() << endl; + file << "RANK " << std::fixed << std::setprecision(shufflesDigits) <second << endl; + } + file << "USE_RPM_FACTOR " << useRPM << endl; + file << "USE_UNIQUE_RPM_FAA " << useRpmFaaScanning << endl; + //print all the sequences that have hit with the motif + if (isOutputSequences){ + SequencesCount hitSequences = (memesIter->second).getHitSequences(); + writeSequenceHits(hitSequences, sequncesRpm, memesIter->first, sequenceHitMotifPath); + } + memesIter++; + } +} + +int main(int argc, char *argv[]) +{ + cxxopts::Options options("Hits", "Calculate hits given mime, cutoffs and sequences"); + options.add_options() + ("m,memes", "Path to memes file", cxxopts::value()) + ("c,cutoffs", "Path to cutoffs file", cxxopts::value()) + ("s,sequences", "Path to sequences file", cxxopts::value()) + ("o,output", "Path to results file", cxxopts::value()) + ("maxMemes", "Limit number of memes to process (0 = all)", cxxopts::value()->default_value("0")) + ("outputSequences", "Write matched sequences (not memory efficient)", cxxopts::value()->default_value("false")) + ("shuffles", "Create shuffles and rate memes by them (0 = disable)", cxxopts::value()->default_value("0")) + ("shufflesPercent", "Percent from shuffle with greatest number of hits (0-1)", cxxopts::value()->default_value("0.2")) + ("shufflesDigits", "Number of digits after the point to print in scanning files", cxxopts::value()->default_value("2")) + ("useFactor", "To multiply by factor hits for normalization", cxxopts::value()->default_value("false")) + ("sequenceHitMotifPath", "Path for results of sequence that had hit with motif", cxxopts::value()->default_value("")) + ("useRpmFaaScanning", "Performance of scanning script with rpm faa file", cxxopts::value()->default_value("false")) + ("v,verbose", "Verbose output", cxxopts::value()->default_value("false")); + auto result = options.parse(argc, argv); + + auto memesPath = result["memes"].as(); + auto cutoffsPath = result["cutoffs"].as(); + auto sequencesPath = result["sequences"].as(); + auto outputPath = result["output"].as(); + auto isOutputSequences = result["outputSequences"].as(); + auto maxMemes = result["maxMemes"].as(); + auto shuffles = result["shuffles"].as(); + auto shufflesPercent = result["shufflesPercent"].as(); + auto shufflesDigits = result["shufflesDigits"].as(); + auto useFactor = result["useFactor"].as(); + auto sequenceHitMotifPath = result["sequenceHitMotifPath"].as(); + auto useRpmFaaScanning = result["useRpmFaaScanning"].as(); + auto isVerbose = result["verbose"].as(); + + auto begin = chrono::steady_clock::now(); + int numSequences; + SequencesRpmMap sequncesRpm; + auto memes = loadMemes(memesPath, maxMemes, isVerbose); + loadCutoffs(cutoffsPath, memes, maxMemes, isVerbose); + SequencesMap sequences = loadSequences(sequencesPath, numSequences, sequncesRpm, useRpmFaaScanning, isVerbose); + auto memesShuffles = createShuffles(memes, shuffles); + getHits(memes, sequences, memesShuffles, isOutputSequences, sequncesRpm, useRpmFaaScanning, isVerbose); + + if (useFactor & !useRpmFaaScanning) { + double rpmFactor = getRpmFactor(numSequences); + cout << "RPM factor: " << rpmFactor << endl; + factorHits(memes, memesShuffles, rpmFactor); + } + + MemeRatingMap memesRating; + if (shuffles) { + memesRating = getRatings(memes, memesShuffles, isVerbose, shufflesPercent); + } + writeResults(memes, memesRating, memesShuffles, outputPath, sequncesRpm, isOutputSequences, sequenceHitMotifPath, useFactor, useRpmFaaScanning, isVerbose, shufflesDigits); + + auto end = chrono::steady_clock::now(); + cout << chrono::duration_cast(end - begin).count() << "[s]" << endl; +} diff --git a/hits_cpp/loadCutoffs.cpp b/hits_cpp/loadCutoffs.cpp new file mode 100644 index 0000000..d98e007 --- /dev/null +++ b/hits_cpp/loadCutoffs.cpp @@ -0,0 +1,35 @@ +#include +#include +#include +#include + +#include "types.hpp" +#include "meme.hpp" +#include "memes.hpp" + +using namespace std; +void loadCutoffs(string cutoffsPath, Memes& memes, int limit = 0, bool verbose = false) { + ifstream file(cutoffsPath); + string line; + + regex pattern("([^\\t]+)\\t([^\\t]+)"); + smatch matches; + Meme* meme; + int total = 0; + + while (getline(file, line)) { + regex_search(line, matches, pattern); + if (matches[1].compare("###") == 0) { + if (limit && total == limit) { + break; + } + meme = &memes.getMemes()[matches[2]]; + total++; + } else { + if (verbose) { + cout << matches[1] << ": " << matches[2] << endl; + } + meme->getCuttofs()[matches[1]] = stod(matches[2]); + } + } +} \ No newline at end of file diff --git a/hits_cpp/loadMemes.cpp b/hits_cpp/loadMemes.cpp new file mode 100644 index 0000000..3cd62e1 --- /dev/null +++ b/hits_cpp/loadMemes.cpp @@ -0,0 +1,71 @@ +#include +#include +#include +#include + +#include "types.hpp" +#include "meme.hpp" +#include "memes.hpp" +#include "trim.hpp" + +using namespace std; + +Memes loadMemes(string memePath, int limit = 0, bool verbose = false) { + ifstream file(memePath); + string line; + + AlphabetMap alphabet; + MemesMap memes; + + regex motifPattern("alength= (\\d+) w= (\\d+) nsites= (\\d+)"); + regex rowPattern("(\\d+\\.?\\d*)"); + auto regexEnd = sregex_iterator(); + smatch matches; + int alength; + int rows; + int total = 0; + + while (getline(file, line)) { + if (line.rfind("MOTIF", 0) == 0) { + Meme meme; + string motif = line.substr(6); + meme.setMotif(rtrim(motif)); + + getline(file, line); + regex_search(line, matches, motifPattern); + alength = stoi(matches[1]); + meme.setALength(alength); + rows = stoi(matches[2]); + meme.setNSites(stoi(matches[3])); + + for (int i = 0; i < rows; i++) { + getline(file, line); + vector row; + sregex_iterator iter(line.begin(), line.end(), rowPattern); + while (iter != regexEnd) { + row.push_back(stod(iter->str())); + iter++; + } + row.push_back(0); // gap + meme.getRows().push_back(row); + } + meme.normalize(); + memes[motif] = meme; + + if (++total == limit) { + break; + } + } else if (line.rfind("ALPHABET=", 0) == 0) { + int start = 10; + int end = line.length() - start; + for (int i = 0; i < end; i++) { + alphabet[line[start + i]] = i; + } + alphabet['-'] = end; + } + } + if (verbose) { + cout << "Total memes: " << memes.size() << endl; + } + return Memes(alphabet, memes); +} diff --git a/hits_cpp/loadSequences.cpp b/hits_cpp/loadSequences.cpp new file mode 100644 index 0000000..9f4e959 --- /dev/null +++ b/hits_cpp/loadSequences.cpp @@ -0,0 +1,66 @@ +#include +#include +#include +#include +#include +#include + +#include "types.hpp" +#include "trim.hpp" + +using namespace std; +SequencesMap loadSequences(string faaPath, int& numSequences, SequencesRpmMap& sequncesRpm, bool useRpmFaaScanning, bool verbose) { + SequencesMap sequences; + ifstream file(faaPath); + string line; + list* sequencesByType; + + regex pattern("^>.+_(.+)$"); + smatch matches; + auto end = sequences.end(); + int count = 0; + double uniquePeptides = 0.0; + string seqType; + while (getline(file, line)) { + if (line[0] == '>') { + if (useRpmFaaScanning) { + replace(line.begin(), line.end(), '_', ' '); // replace '_' by ' ' + istringstream iss(line); + vector result; + for (string s;iss>>s;) { + result.push_back(s); + } + seqType = result[3]; + uniquePeptides = stod(result[7]); + } else { + auto lastIndex = line.find_last_of('_'); + seqType = line.substr(lastIndex + 1); + } + rtrim(seqType); + auto iter = sequences.find(seqType); + if (iter == end) { + sequencesByType = new list(); + sequences[seqType] = sequencesByType; + } else { + sequencesByType = iter->second; + } + + getline(file, line); + sequencesByType->push_back(line); + if (useRpmFaaScanning) { + sequncesRpm[line] = uniquePeptides; + } else { + auto iter = sequncesRpm.find(line); + if (iter == sequncesRpm.end()) { + sequncesRpm[line] = 1.0; + } else { + iter->second++; + } + } + count++; + } + } + numSequences = count; + cout << "total sequences in file: " << count << endl; + return sequences; +} diff --git a/hits_cpp/meme.hpp b/hits_cpp/meme.hpp new file mode 100644 index 0000000..1ee303a --- /dev/null +++ b/hits_cpp/meme.hpp @@ -0,0 +1,99 @@ +#pragma once +#include "types.hpp" + +class Meme { +public: + Meme() : _hitCount(0.0) { + + } + + Meme(const Meme& other) : + _motif(other._motif), + _alength(other._alength), + _nsites(other._nsites), + _hitCount(other._hitCount), + _rows(other._rows), + _cutoffs(other._cutoffs), + _hitSequences(other._hitSequences) { + } + + string getMotif() { + return this->_motif; + } + + void setMotif(string motif) { + this->_motif = motif; + } + + int getALength() { + return this->_alength; + } + + void setALength(int alength) { + this->_alength = alength; + } + + int getNSites() { + return this->_nsites; + } + + void setNSites(int nsites) { + this->_nsites = nsites; + } + + MemeRows& getRows() { + return this->_rows; + } + + CutoffsMap& getCuttofs() { + return this->_cutoffs; + } + + SequencesCount& getHitSequences() { + return this->_hitSequences; + } + + int getHitCount() { + return this->_hitCount; + } + + void factorHitCount(float factor) { + this->_hitCount *=factor; + } + + void addHitSequence(string& sequence, bool isStoreSequences = true, double count = 1.0) { + this->_hitCount += count; + if (isStoreSequences) { + if (this->_hitSequences.find(sequence) == this->_hitSequences.end()) { + this->_hitSequences[sequence] = count; + } else { + this->_hitSequences[sequence] += count; + } + } + } + + void normalize() { + if (this->_nsites == 0) { + return; + } + auto rows = this->_rows.size(); + auto columns = this->_alength + 1; + + auto iter = this->_rows.begin(); + auto end = this->_rows.end(); + + for (auto rowsIter = this->_rows.begin(); rowsIter != this->_rows.end(); ++rowsIter) { + for (auto columnsIter = rowsIter->begin(); columnsIter != rowsIter->end(); ++columnsIter) { + *columnsIter = ((*columnsIter * this->_nsites) + 1) / (this->_nsites + columns); + } + } + } +private: + string _motif; + int _alength; + int _nsites; + long double _hitCount; + MemeRows _rows; + CutoffsMap _cutoffs; + SequencesCount _hitSequences; +}; diff --git a/hits_cpp/memes.hpp b/hits_cpp/memes.hpp new file mode 100644 index 0000000..2a473a9 --- /dev/null +++ b/hits_cpp/memes.hpp @@ -0,0 +1,20 @@ +#pragma once +#include "types.hpp" + +class Memes { +public: + Memes(AlphabetMap alphabet, MemesMap memes) : + _alphabet(alphabet), _memes(memes) { + } + + AlphabetMap& getAlphabet() { + return this->_alphabet; + } + + MemesMap& getMemes() { + return this->_memes; + } +private: + AlphabetMap _alphabet; + MemesMap _memes; +}; diff --git a/hits_cpp/shufflePatterns.hpp b/hits_cpp/shufflePatterns.hpp new file mode 100644 index 0000000..f18cfdb --- /dev/null +++ b/hits_cpp/shufflePatterns.hpp @@ -0,0 +1,4534 @@ +#pragma once + +#include "types.hpp" + +ShufflesMap _shuffle_sequences = { + { 1, { + {0}, + } + }, + { 2, { + {1,0}, + } + }, + { 3, { + {2,0,1}, + {2,1,0}, + {1,2,0}, + } + }, + { 4, { + {2,3,1,0}, + {3,2,1,0}, + {3,2,0,1}, + } + }, + { 5, { + {4,3,0,2,1}, + {4,3,2,1,0}, + {2,4,3,1,0}, + {4,3,1,0,2}, + {2,4,3,0,1}, + {3,4,0,2,1}, + {3,2,4,0,1}, + {4,3,1,2,0}, + {3,2,4,1,0}, + {3,4,1,0,2}, + {4,2,3,1,0}, + } + }, + { 6, { + {5,4,3,1,0,2}, + {4,5,3,1,0,2}, + {4,3,5,1,0,2}, + {4,3,5,1,2,0}, + {5,3,4,0,2,1}, + {3,5,4,2,0,1}, + {5,4,3,1,2,0}, + {3,5,4,0,2,1}, + {4,3,5,0,2,1}, + {5,4,3,2,1,0}, + {3,5,4,1,2,0}, + {5,4,3,2,0,1}, + {3,5,4,2,1,0}, + {4,5,3,2,1,0}, + {3,5,4,1,0,2}, + {4,3,5,2,0,1}, + {4,5,3,0,2,1}, + {5,3,4,2,1,0}, + {5,4,3,0,2,1}, + {4,3,5,2,1,0}, + {5,3,4,1,0,2}, + } + }, + { 7, { + {6,4,3,5,2,1,0}, + {4,6,5,3,0,2,1}, + {5,6,4,2,1,0,3}, + {5,6,4,1,0,3,2}, + {6,5,4,3,2,1,0}, + {4,6,3,5,0,2,1}, + {6,5,4,0,2,1,3}, + {4,3,6,5,0,2,1}, + {6,5,4,0,3,1,2}, + {6,3,5,4,2,1,0}, + {3,5,4,6,2,1,0}, + {5,4,6,3,0,2,1}, + {6,4,5,2,1,3,0}, + {3,6,4,5,0,2,1}, + {5,4,6,3,1,0,2}, + {3,5,4,6,2,0,1}, + {3,4,6,5,1,0,2}, + {5,3,4,6,2,1,0}, + {4,6,5,1,0,2,3}, + {5,4,6,2,1,0,3}, + {5,3,6,4,2,0,1}, + {5,4,6,0,1,3,2}, + {5,6,4,1,3,0,2}, + {4,6,5,1,0,3,2}, + {4,5,3,6,2,1,0}, + {6,4,5,0,3,2,1}, + {6,3,5,4,1,2,0}, + {6,3,5,4,1,0,2}, + {6,5,3,4,2,1,0}, + {3,6,5,4,2,0,1}, + {5,4,6,2,1,3,0}, + {6,4,3,5,1,2,0}, + {5,4,3,6,1,2,0}, + {4,6,5,2,1,3,0}, + {4,6,5,0,3,2,1}, + {4,5,3,6,1,0,2}, + {3,4,6,5,2,1,0}, + {4,3,6,5,2,1,0}, + {3,6,4,5,1,0,2}, + {4,3,5,6,0,2,1}, + {6,5,4,0,3,2,1}, + {5,3,4,6,0,2,1}, + {5,3,6,4,2,1,0}, + {5,4,6,3,2,1,0}, + {4,6,5,1,2,0,3}, + {4,3,6,5,1,0,2}, + {6,5,3,4,0,2,1}, + {5,4,3,6,2,1,0}, + {4,6,5,3,2,1,0}, + {3,5,4,6,1,2,0}, + {6,5,3,4,1,0,2}, + {5,3,6,4,0,2,1}, + {5,4,6,1,0,3,2}, + {5,4,3,6,1,0,2}, + {6,5,4,1,0,3,2}, + {6,4,3,5,1,0,2}, + {4,6,5,0,2,3,1}, + {5,6,4,0,3,2,1}, + {5,6,4,1,3,2,0}, + {5,4,3,6,0,2,1}, + } + }, + { 8, { + {5,4,7,6,2,1,3,0}, + {6,5,7,4,3,1,0,2}, + {6,4,7,5,1,3,2,0}, + {7,6,4,5,1,3,2,0}, + {4,7,5,6,2,1,3,0}, + {4,7,6,5,0,2,3,1}, + {4,7,6,5,3,1,2,0}, + {6,5,7,4,0,2,3,1}, + {4,7,6,5,0,3,2,1}, + {4,6,5,7,2,0,1,3}, + {6,5,7,4,2,1,0,3}, + {6,4,5,7,0,2,1,3}, + {5,4,7,6,2,0,1,3}, + {7,4,6,5,1,3,2,0}, + {6,5,4,7,2,1,0,3}, + {5,7,4,6,0,1,3,2}, + {7,4,6,5,3,1,0,2}, + {7,6,4,5,3,2,1,0}, + {4,5,7,6,0,2,1,3}, + {6,4,5,7,2,1,0,3}, + {5,4,6,7,2,0,3,1}, + {6,4,7,5,2,0,3,1}, + {7,4,6,5,1,0,2,3}, + {5,7,6,4,1,3,2,0}, + {6,5,7,4,0,1,3,2}, + {7,6,5,4,1,3,2,0}, + {4,7,5,6,0,2,1,3}, + {4,6,5,7,3,0,2,1}, + {6,5,7,4,3,0,2,1}, + {5,7,6,4,0,3,2,1}, + {5,4,6,7,3,0,2,1}, + {7,4,6,5,0,3,2,1}, + {5,4,6,7,1,3,0,2}, + {5,7,4,6,0,3,2,1}, + {6,4,7,5,3,0,2,1}, + {7,5,4,6,1,3,2,0}, + {7,5,6,4,0,2,1,3}, + {5,4,7,6,3,2,1,0}, + {7,6,5,4,3,2,0,1}, + {6,5,4,7,1,0,2,3}, + {7,4,6,5,1,0,3,2}, + {7,6,5,4,3,2,1,0}, + {5,7,4,6,0,2,1,3}, + {4,6,5,7,0,2,3,1}, + {7,6,5,4,1,0,3,2}, + {4,7,6,5,0,2,1,3}, + {5,7,4,6,1,3,2,0}, + {6,4,7,5,1,3,0,2}, + {4,6,5,7,3,2,1,0}, + {5,4,7,6,0,2,3,1}, + {7,5,6,4,2,1,3,0}, + {6,4,7,5,3,1,0,2}, + {4,6,7,5,2,1,3,0}, + {5,6,4,7,3,0,2,1}, + {6,5,7,4,1,0,2,3}, + {7,4,6,5,1,2,0,3}, + {4,6,5,7,0,2,1,3}, + {5,6,4,7,1,3,0,2}, + {7,5,6,4,3,1,0,2}, + {5,7,4,6,2,1,0,3}, + {6,4,7,5,0,3,2,1}, + {5,7,4,6,2,0,3,1}, + {4,7,6,5,3,2,0,1}, + {6,4,5,7,3,1,0,2}, + {7,6,4,5,2,1,3,0}, + {4,6,5,7,1,2,0,3}, + {5,7,6,4,2,0,3,1}, + {4,5,7,6,3,1,0,2}, + {7,5,4,6,3,1,2,0}, + {7,5,4,6,0,2,1,3}, + {7,6,5,4,0,2,1,3}, + {6,4,7,5,1,0,3,2}, + {5,7,4,6,1,3,0,2}, + {6,5,7,4,3,1,2,0}, + {6,4,7,5,1,2,0,3}, + {7,5,4,6,2,0,3,1}, + {4,7,6,5,0,3,1,2}, + {4,5,7,6,2,1,0,3}, + {4,5,7,6,3,0,2,1}, + {7,5,6,4,1,3,0,2}, + {6,4,7,5,2,1,3,0}, + {4,6,5,7,3,2,0,1}, + {7,5,4,6,2,1,3,0}, + {4,6,5,7,0,3,1,2}, + {4,6,5,7,0,3,2,1}, + {7,6,5,4,2,0,1,3}, + } + }, + { 9, { + {6,4,7,5,8,3,2,0,1}, + {8,4,7,5,6,2,1,3,0}, + {6,7,5,8,3,1,4,2,0}, + {4,8,7,6,5,2,3,1,0}, + {5,4,7,6,8,3,1,2,0}, + {4,6,8,7,5,1,3,2,0}, + {4,6,5,7,8,1,3,0,2}, + {4,7,5,6,8,1,3,0,2}, + {5,8,6,4,7,3,0,2,1}, + {5,7,6,8,3,0,2,1,4}, + {6,8,7,5,3,1,2,0,4}, + {8,7,6,5,2,4,3,1,0}, + {7,5,4,6,8,1,3,0,2}, + {7,5,6,8,2,1,4,0,3}, + {5,8,7,6,0,2,1,4,3}, + {5,7,8,6,2,4,3,1,0}, + {6,4,8,7,5,0,2,1,3}, + {8,7,4,6,5,0,3,2,1}, + {6,5,7,8,1,4,0,3,2}, + {6,8,5,7,4,2,1,3,0}, + {8,5,7,6,2,3,1,4,0}, + {8,5,4,7,6,0,2,3,1}, + {5,8,7,6,2,4,0,3,1}, + {8,7,6,5,4,0,2,1,3}, + {5,8,7,6,1,3,4,2,0}, + {8,7,6,5,2,1,4,3,0}, + {7,6,8,4,5,3,1,0,2}, + {8,5,4,7,6,3,2,1,0}, + {6,8,5,7,4,1,3,2,0}, + {6,8,7,5,4,3,0,2,1}, + {7,6,8,5,0,3,1,4,2}, + {4,7,5,6,8,0,2,1,3}, + {6,5,8,4,7,3,1,2,0}, + {4,6,8,7,5,3,1,0,2}, + {6,5,7,4,8,3,0,2,1}, + {8,6,5,7,1,4,3,2,0}, + {5,8,7,6,0,3,1,2,4}, + {4,8,5,7,6,3,1,2,0}, + {8,6,5,7,0,2,1,4,3}, + {5,6,8,4,7,3,1,0,2}, + {5,7,6,8,4,3,0,2,1}, + {6,8,4,5,7,2,1,3,0}, + {8,6,5,7,2,0,3,1,4}, + {6,8,5,7,0,2,4,1,3}, + {7,5,8,6,4,2,0,3,1}, + {7,6,4,5,8,3,1,0,2}, + {6,5,7,8,3,1,0,2,4}, + {6,8,5,7,0,1,3,2,4}, + {8,6,5,7,1,4,2,3,0}, + {6,5,8,7,4,2,1,3,0}, + {7,4,8,5,6,2,1,0,3}, + {5,7,6,4,8,0,2,1,3}, + {7,4,6,5,8,1,0,2,3}, + {6,5,7,4,8,2,0,1,3}, + {6,5,4,8,7,1,0,3,2}, + {5,7,4,6,8,0,2,1,3}, + {5,6,4,8,7,3,1,0,2}, + {6,4,7,5,8,1,0,2,3}, + {5,4,7,6,8,2,1,0,3}, + {6,5,8,7,4,3,2,1,0}, + {5,6,8,7,1,0,4,3,2}, + {8,7,6,5,4,3,2,1,0}, + {6,8,5,7,4,1,0,3,2}, + {5,4,7,6,8,2,0,3,1}, + {7,8,5,4,6,0,2,1,3}, + {6,5,4,8,7,1,0,2,3}, + {4,7,6,8,5,1,3,2,0}, + {6,8,7,5,3,0,4,2,1}, + {5,7,6,8,4,0,2,1,3}, + {7,6,5,4,8,2,1,0,3}, + {6,7,5,8,0,4,3,2,1}, + {4,6,5,8,7,3,1,0,2}, + {7,4,8,5,6,3,1,0,2}, + {6,5,7,4,8,3,2,0,1}, + {8,7,6,5,0,2,4,1,3}, + {6,5,8,7,4,1,0,3,2}, + {6,8,7,5,2,1,3,4,0}, + {6,8,7,5,0,2,4,3,1}, + {7,5,8,6,1,0,4,3,2}, + {5,7,4,6,8,1,0,2,3}, + {4,7,5,8,6,2,0,1,3}, + {8,5,7,4,6,1,3,2,0}, + {7,6,8,5,4,3,1,0,2}, + {6,8,7,5,2,1,4,0,3}, + {7,5,4,6,8,0,1,3,2}, + {6,8,5,7,3,0,2,4,1}, + {6,8,5,7,0,2,4,3,1}, + {8,6,7,5,0,3,1,4,2}, + {6,8,5,7,4,2,0,3,1}, + {5,8,6,7,0,4,2,1,3}, + {8,6,4,7,5,0,3,2,1}, + {7,5,8,6,3,2,0,4,1}, + {5,8,7,6,4,0,3,2,1}, + {5,6,8,7,3,0,2,4,1}, + {7,5,8,6,4,1,3,0,2}, + } + }, + { 10, { + {9,8,6,5,7,3,2,0,1,4}, + {9,5,7,6,8,0,4,2,1,3}, + {6,8,7,5,9,1,3,4,0,2}, + {5,6,9,8,7,2,1,0,4,3}, + {5,9,8,7,6,2,1,4,3,0}, + {5,9,7,6,8,2,3,1,4,0}, + {6,9,7,5,8,0,4,2,3,1}, + {7,8,6,9,5,3,0,2,4,1}, + {6,5,7,9,8,3,0,4,2,1}, + {9,5,8,7,6,1,3,2,0,4}, + {6,8,5,9,7,2,4,3,1,0}, + {6,5,7,9,8,0,4,2,1,3}, + {6,5,9,8,7,4,0,1,3,2}, + {9,5,8,7,6,3,0,4,2,1}, + {8,7,9,6,5,1,3,4,0,2}, + {9,8,6,7,5,0,2,4,3,1}, + {5,6,9,8,7,1,4,3,2,0}, + {5,9,6,8,7,1,2,4,3,0}, + {7,6,5,8,9,4,1,0,3,2}, + {6,9,7,5,8,3,0,1,4,2}, + {7,6,5,9,8,1,4,2,3,0}, + {6,5,7,9,8,1,3,2,0,4}, + {5,7,6,9,8,0,2,1,4,3}, + {9,5,8,6,7,1,3,0,2,4}, + {6,8,7,5,9,0,2,4,1,3}, + {9,6,8,7,5,1,2,4,3,0}, + {9,7,6,5,8,0,2,1,3,4}, + {9,6,5,7,8,4,1,3,2,0}, + {5,6,8,7,9,3,1,4,2,0}, + {5,9,8,7,6,2,0,3,1,4}, + {5,6,8,7,9,1,3,0,4,2}, + {5,9,7,6,8,0,3,2,4,1}, + {7,5,9,6,8,2,1,4,0,3}, + {8,5,7,9,6,3,0,2,4,1}, + {9,5,7,6,8,3,1,2,4,0}, + {7,9,6,8,5,4,2,3,1,0}, + {8,5,7,6,9,1,0,4,3,2}, + {6,8,7,5,9,3,4,1,0,2}, + {5,9,6,8,7,4,2,1,0,3}, + {6,9,7,5,8,4,0,2,3,1}, + {5,6,8,7,9,1,0,3,2,4}, + {6,8,5,7,9,0,3,1,4,2}, + {8,6,5,7,9,3,1,4,2,0}, + {9,5,8,7,6,2,0,3,1,4}, + {8,5,7,9,6,4,1,3,0,2}, + {6,8,5,7,9,0,4,3,2,1}, + {9,5,7,6,8,1,3,0,2,4}, + {5,8,6,7,9,3,0,4,2,1}, + {9,5,6,8,7,3,2,4,1,0}, + {7,5,9,8,6,1,4,3,0,2}, + {9,7,5,6,8,2,4,1,3,0}, + {8,6,7,5,9,2,4,3,1,0}, + {8,5,9,7,6,1,0,4,2,3}, + {9,5,7,6,8,1,3,2,4,0}, + {8,7,9,6,5,4,3,1,0,2}, + {6,8,7,5,9,3,0,2,1,4}, + {9,8,7,5,6,2,1,4,0,3}, + {6,5,8,9,7,4,2,1,0,3}, + {9,7,6,8,5,2,3,1,4,0}, + {7,5,6,9,8,3,0,2,1,4}, + {9,8,6,5,7,0,2,4,1,3}, + {8,6,5,7,9,2,3,1,0,4}, + {8,7,6,5,9,0,2,1,3,4}, + {7,5,9,8,6,2,1,4,0,3}, + {6,9,7,5,8,2,0,3,4,1}, + {6,9,7,5,8,2,1,0,3,4}, + {7,6,9,8,5,1,2,4,0,3}, + {9,5,8,7,6,3,0,2,1,4}, + {7,6,5,9,8,2,0,3,4,1}, + {9,7,6,8,5,4,0,3,1,2}, + {8,7,6,9,5,4,2,1,0,3}, + {8,7,9,5,6,4,3,1,0,2}, + {6,8,7,9,5,2,1,3,0,4}, + {5,8,7,9,6,1,4,2,3,0}, + {6,8,5,7,9,4,0,3,1,2}, + {5,9,8,7,6,0,4,3,2,1}, + {8,5,7,6,9,3,1,0,2,4}, + {5,8,6,9,7,0,2,4,1,3}, + {5,8,7,6,9,3,0,4,2,1}, + {5,9,8,7,6,4,3,0,2,1}, + {6,8,7,5,9,3,2,4,0,1}, + {6,7,9,8,5,2,4,3,1,0}, + {5,8,7,6,9,1,3,0,2,4}, + {7,5,6,9,8,0,2,1,4,3}, + {5,7,9,8,6,4,0,1,3,2}, + {8,6,5,7,9,4,3,1,2,0}, + {6,7,9,5,8,4,3,1,0,2}, + {8,5,7,6,9,3,1,2,4,0}, + {7,9,5,8,6,2,0,4,3,1}, + {7,5,9,6,8,3,2,4,1,0}, + {7,5,8,6,9,1,3,4,0,2}, + {6,5,8,7,9,1,3,4,2,0}, + {8,7,6,9,5,4,1,3,0,2}, + {7,5,9,6,8,0,4,2,1,3}, + {7,6,8,5,9,3,1,0,2,4}, + {9,8,7,6,5,3,1,0,2,4}, + {9,6,8,7,5,4,0,3,2,1}, + {8,6,5,9,7,1,0,4,3,2}, + } + }, + { 11, { + {10,9,5,8,6,7,3,0,2,1,4}, + {9,10,7,6,8,1,5,2,4,0,3}, + {6,9,5,10,7,8,2,4,0,3,1}, + {8,7,6,9,5,10,2,1,4,3,0}, + {9,6,5,8,7,10,1,4,3,0,2}, + {8,9,7,6,10,4,3,0,2,5,1}, + {9,6,8,10,7,1,3,5,4,0,2}, + {6,9,8,10,7,2,0,4,1,5,3}, + {8,7,10,5,9,6,1,4,3,0,2}, + {8,6,10,5,7,9,1,4,0,3,2}, + {8,7,5,10,6,9,1,4,0,2,3}, + {8,9,7,6,10,1,5,2,0,4,3}, + {5,7,10,9,8,6,0,2,1,3,4}, + {6,10,7,9,8,1,5,2,4,3,0}, + {5,9,7,10,6,8,0,4,3,2,1}, + {8,7,10,9,6,5,4,2,0,3,1}, + {10,6,8,5,7,9,4,0,2,1,3}, + {8,10,9,5,7,6,2,0,1,4,3}, + {6,5,9,8,10,7,3,4,1,0,2}, + {5,9,6,10,8,7,2,4,0,3,1}, + {10,8,7,6,9,4,2,0,1,3,5}, + {9,6,8,7,10,2,5,0,4,1,3}, + {9,8,10,6,5,7,3,1,4,0,2}, + {10,6,9,7,8,4,1,5,2,0,3}, + {6,8,7,10,9,4,0,3,1,5,2}, + {10,6,8,7,9,4,3,5,2,1,0}, + {8,9,7,6,10,3,2,1,0,5,4}, + {6,8,7,10,9,5,0,2,4,1,3}, + {10,6,5,9,8,7,3,2,0,1,4}, + {10,6,9,8,7,1,3,0,5,4,2}, + {9,8,6,10,7,0,4,3,2,5,1}, + {9,6,5,8,10,7,1,0,3,2,4}, + {8,7,6,9,10,2,5,0,3,1,4}, + {9,7,5,10,6,8,2,1,3,4,0}, + {6,8,7,9,10,4,3,2,1,0,5}, + {7,6,9,8,10,1,5,2,3,0,4}, + {10,7,9,6,5,8,1,3,2,4,0}, + {8,10,7,9,6,3,0,5,4,2,1}, + {5,8,7,9,6,10,1,4,2,3,0}, + {8,7,10,6,9,2,5,4,0,3,1}, + {9,8,5,7,10,6,0,2,1,3,4}, + {7,8,5,9,6,10,0,3,1,4,2}, + {9,5,8,10,6,7,2,0,4,3,1}, + {7,8,10,9,6,0,3,2,4,1,5}, + {5,9,8,10,7,6,4,1,2,0,3}, + {6,8,10,7,9,2,0,3,1,4,5}, + {7,6,10,8,5,9,3,0,2,4,1}, + {5,7,9,6,10,8,3,1,4,2,0}, + {10,6,8,7,9,4,1,3,2,5,0}, + {7,9,6,10,8,3,0,4,2,1,5}, + {6,9,8,5,10,7,0,3,2,1,4}, + {6,5,8,10,9,7,2,1,0,4,3}, + {10,6,7,9,8,1,0,2,4,3,5}, + {10,7,8,6,9,0,3,1,4,2,5}, + {5,10,6,7,9,8,3,1,0,2,4}, + {6,8,10,9,7,3,5,4,1,0,2}, + {6,10,7,9,8,5,0,2,4,3,1}, + {5,8,6,10,9,7,3,4,1,0,2}, + {8,7,6,10,5,9,0,4,2,1,3}, + {6,8,5,7,10,9,4,3,1,2,0}, + {9,7,6,8,10,5,2,1,3,0,4}, + {10,6,5,9,7,8,1,3,2,4,0}, + {8,10,7,9,6,4,5,3,1,0,2}, + {5,6,9,8,7,10,4,0,3,2,1}, + {8,9,7,10,6,4,1,3,0,5,2}, + {5,7,6,9,10,8,2,0,3,1,4}, + {8,5,10,9,7,6,2,4,3,1,0}, + {8,5,9,7,10,6,3,1,4,0,2}, + {9,5,10,7,8,6,1,3,0,4,2}, + {5,7,10,6,9,8,1,3,4,0,2}, + {8,10,7,9,6,4,5,2,1,0,3}, + {10,8,7,9,6,1,3,2,0,5,4}, + {5,8,6,10,7,9,2,0,3,4,1}, + {9,8,10,6,5,7,2,4,3,1,0}, + {9,8,10,6,5,7,1,3,0,2,4}, + {10,9,7,8,6,2,5,4,3,1,0}, + {9,5,8,7,10,6,2,1,4,0,3}, + {10,7,9,6,8,5,3,1,4,2,0}, + {9,8,10,7,6,2,5,1,3,0,4}, + {8,9,7,10,6,1,3,5,4,0,2}, + {5,10,6,7,9,8,4,1,3,0,2}, + {9,8,10,7,6,2,4,3,1,0,5}, + {10,7,6,9,8,1,5,4,3,2,0}, + {9,7,6,5,8,10,4,1,0,2,3}, + {6,9,7,10,8,3,2,0,5,1,4}, + {9,6,10,7,5,8,1,3,0,4,2}, + {5,7,9,8,10,6,0,2,4,1,3}, + {8,7,9,6,10,5,3,1,0,2,4}, + {7,9,8,6,10,3,1,0,2,5,4}, + {8,7,10,9,6,1,3,2,4,0,5}, + {8,10,7,6,9,5,4,2,0,3,1}, + {10,6,7,9,8,2,5,3,1,4,0}, + {9,8,7,10,6,0,5,1,4,3,2}, + {8,6,10,7,9,4,1,5,2,0,3}, + {5,7,9,8,10,6,0,2,3,1,4}, + {6,8,9,7,10,2,5,3,1,0,4}, + {9,7,10,8,6,4,5,1,3,0,2}, + {9,10,6,8,7,4,0,2,1,3,5}, + {5,8,7,10,6,9,4,3,1,0,2}, + } + }, + { 12, { + {8,7,10,6,9,11,3,0,2,4,5,1}, + {8,7,6,9,11,10,2,1,0,3,5,4}, + {8,6,7,10,9,11,3,5,4,1,0,2}, + {6,11,9,8,7,10,0,5,3,2,4,1}, + {8,7,11,10,6,9,2,3,0,5,1,4}, + {9,11,8,10,6,7,2,5,1,3,0,4}, + {8,7,9,11,6,10,4,1,0,2,5,3}, + {11,7,6,10,9,8,5,1,3,0,2,4}, + {6,9,8,11,7,10,0,3,1,5,4,2}, + {6,8,11,9,7,10,2,4,1,5,0,3}, + {8,6,9,10,7,11,1,4,2,5,0,3}, + {11,9,7,6,8,10,4,1,3,5,2,0}, + {9,10,8,7,11,6,5,1,3,0,2,4}, + {10,6,8,9,11,7,5,1,0,4,3,2}, + {6,8,10,7,9,11,1,5,2,4,3,0}, + {9,8,6,10,7,11,4,3,1,0,2,5}, + {9,7,10,6,11,8,3,1,5,4,2,0}, + {8,6,7,10,9,11,1,0,3,5,2,4}, + {6,7,9,8,11,10,4,1,3,2,5,0}, + {11,7,10,9,6,8,0,2,3,5,1,4}, + {9,8,10,7,11,6,5,3,1,0,2,4}, + {8,10,6,7,11,9,3,0,5,2,1,4}, + {10,6,11,7,9,8,2,4,1,3,0,5}, + {11,9,6,8,10,7,5,2,0,3,1,4}, + {7,10,8,11,9,6,0,5,3,2,4,1}, + {9,7,6,8,11,10,0,2,5,4,3,1}, + {8,7,11,10,9,6,0,5,3,1,4,2}, + {10,9,6,11,8,7,0,1,5,3,2,4}, + {6,11,10,9,8,7,3,0,5,4,2,1}, + {8,7,6,10,9,11,1,4,5,3,0,2}, + {9,6,10,11,8,7,3,2,4,0,5,1}, + {8,7,6,11,10,9,5,1,0,4,2,3}, + {7,6,8,10,9,11,1,0,3,4,2,5}, + {10,6,7,11,9,8,4,0,3,2,5,1}, + {7,9,11,6,8,10,3,2,0,5,1,4}, + {10,7,6,9,11,8,0,3,5,4,2,1}, + {7,10,9,8,11,6,1,0,2,4,5,3}, + {10,9,8,6,7,11,2,1,5,4,0,3}, + {8,10,6,11,7,9,0,2,5,3,1,4}, + {8,10,7,9,11,6,5,2,4,3,1,0}, + {9,7,8,11,10,6,3,1,0,5,4,2}, + {7,9,6,11,8,10,2,0,4,5,1,3}, + {7,9,8,11,6,10,4,1,3,5,2,0}, + {10,6,11,8,7,9,5,3,0,2,4,1}, + {10,9,6,7,11,8,4,0,5,2,1,3}, + {8,10,11,7,9,6,3,2,5,4,1,0}, + {6,8,11,9,7,10,5,2,1,0,3,4}, + {9,6,8,10,11,7,1,3,2,5,0,4}, + {7,10,9,11,6,8,2,1,0,4,5,3}, + {9,8,10,6,11,7,4,3,2,1,5,0}, + {6,7,9,11,8,10,0,5,3,2,1,4}, + {9,11,8,6,7,10,0,3,5,2,4,1}, + {9,7,10,8,6,11,5,3,1,0,2,4}, + {9,6,8,7,11,10,3,1,5,2,0,4}, + {7,11,10,6,9,8,3,5,4,0,2,1}, + {9,11,8,6,7,10,3,2,1,0,5,4}, + {8,10,9,6,7,11,4,2,0,3,1,5}, + {9,8,7,10,6,11,4,2,3,5,1,0}, + {6,8,7,11,10,9,5,4,3,2,1,0}, + {10,6,11,7,9,8,5,4,0,2,3,1}, + {11,10,7,6,9,8,0,3,1,5,4,2}, + {10,9,8,6,11,7,3,5,1,2,0,4}, + {8,6,7,11,10,9,0,2,4,3,1,5}, + {7,9,10,8,6,11,5,1,3,0,4,2}, + {9,8,10,7,6,11,4,2,1,0,5,3}, + {8,11,10,7,9,6,4,1,0,2,5,3}, + {8,6,10,7,11,9,0,4,5,2,1,3}, + {6,10,9,8,7,11,4,3,0,2,1,5}, + {9,8,11,6,10,7,3,5,1,0,4,2}, + {10,7,9,8,6,11,5,1,0,3,4,2}, + {6,9,11,10,8,7,5,3,0,1,4,2}, + {9,6,7,11,8,10,3,0,5,2,1,4}, + {11,10,8,6,7,9,3,2,1,5,4,0}, + {10,9,8,7,6,11,4,3,2,1,0,5}, + {9,11,6,8,7,10,0,4,3,2,5,1}, + {11,8,10,6,9,7,5,0,3,1,4,2}, + {8,7,10,6,11,9,0,2,5,3,1,4}, + {11,6,9,8,7,10,1,4,5,3,2,0}, + {11,6,10,8,9,7,0,4,3,5,2,1}, + {7,10,9,8,11,6,4,1,3,0,5,2}, + {6,11,9,8,10,7,4,5,3,2,1,0}, + {8,7,11,6,10,9,3,1,0,5,4,2}, + {6,10,8,7,11,9,2,3,0,5,1,4}, + {9,6,10,8,11,7,3,5,1,0,2,4}, + {10,8,9,7,11,6,5,3,0,2,1,4}, + {11,6,10,8,9,7,0,2,5,1,4,3}, + {6,10,7,9,11,8,3,4,0,2,1,5}, + {11,7,10,6,9,8,1,5,3,0,2,4}, + {9,11,6,8,10,7,4,1,0,2,3,5}, + {8,6,9,11,7,10,1,4,0,5,2,3}, + {8,11,6,10,9,7,5,3,0,2,4,1}, + {9,11,8,6,10,7,0,5,3,2,4,1}, + {8,6,9,11,10,7,5,0,3,2,4,1}, + {6,8,7,10,11,9,4,1,3,0,2,5}, + {9,11,8,7,6,10,1,3,0,4,5,2}, + {6,10,7,11,9,8,3,4,0,2,5,1}, + {9,11,8,10,7,6,4,2,1,0,5,3}, + {6,10,9,8,7,11,0,4,3,2,1,5}, + {10,6,7,11,9,8,4,3,1,5,2,0}, + {9,7,6,8,11,10,3,2,4,1,5,0}, + } + }, + { 13, { + {8,11,10,9,12,7,1,6,4,3,5,2,0}, + {10,11,8,12,9,7,5,3,0,4,2,6,1}, + {12,10,6,11,9,8,7,1,4,2,0,5,3}, + {6,9,8,11,7,10,12,1,5,3,0,2,4}, + {7,11,10,9,12,8,1,0,5,2,4,6,3}, + {11,6,12,8,7,10,9,4,3,1,5,0,2}, + {7,10,9,12,8,11,6,5,4,2,1,3,0}, + {8,11,10,6,12,9,7,3,0,4,1,2,5}, + {12,10,8,11,9,7,6,1,0,5,4,3,2}, + {7,6,10,12,9,11,8,0,5,3,2,4,1}, + {7,12,11,9,8,10,6,2,1,3,5,0,4}, + {10,7,12,11,8,9,2,1,3,5,0,4,6}, + {6,12,11,9,8,7,10,5,3,4,0,2,1}, + {9,12,7,6,10,8,11,3,1,0,2,4,5}, + {12,11,10,7,9,8,1,0,2,6,3,5,4}, + {6,9,12,11,10,8,7,0,2,1,4,3,5}, + {7,12,10,6,9,11,8,3,4,0,2,1,5}, + {10,12,7,9,11,8,0,3,6,4,2,1,5}, + {9,12,10,7,11,8,6,3,5,0,4,2,1}, + {12,8,7,9,11,6,10,0,4,1,3,2,5}, + {7,11,8,9,12,10,5,3,0,2,1,4,6}, + {7,11,10,8,12,9,4,3,6,5,1,0,2}, + {9,8,11,10,12,7,4,6,5,3,1,2,0}, + {8,7,12,11,6,10,9,1,5,2,4,0,3}, + {9,8,11,10,12,7,6,5,1,0,3,2,4}, + {7,12,8,6,10,9,11,0,5,2,1,3,4}, + {10,9,7,12,11,8,1,3,5,6,2,4,0}, + {9,11,8,7,10,12,3,0,2,6,4,1,5}, + {11,7,9,12,10,6,8,2,3,1,5,0,4}, + {6,11,8,10,9,7,12,3,2,4,5,1,0}, + {9,7,12,11,8,10,1,0,4,3,5,6,2}, + {10,12,9,8,11,7,4,5,3,1,0,6,2}, + {11,7,8,10,9,6,12,5,0,4,2,1,3}, + {8,11,9,6,10,12,7,1,5,2,0,3,4}, + {10,12,7,11,8,6,9,2,0,4,3,5,1}, + {10,8,12,11,6,9,7,5,0,3,2,1,4}, + {10,7,9,8,11,6,12,0,3,5,4,2,1}, + {12,11,10,8,7,6,9,1,3,0,2,4,5}, + {8,11,9,7,10,12,4,2,0,5,3,1,6}, + {11,6,8,12,10,9,7,4,0,3,2,5,1}, + {9,10,12,7,6,11,8,4,0,2,5,3,1}, + {11,10,6,8,12,7,9,5,1,3,0,2,4}, + {8,12,9,11,10,7,6,5,0,2,4,3,1}, + {8,10,7,12,11,9,0,3,6,1,4,5,2}, + {10,8,7,9,12,6,11,2,5,1,0,3,4}, + {11,10,6,9,8,12,7,5,4,3,1,0,2}, + {9,7,11,8,10,12,4,1,6,2,3,0,5}, + {11,7,10,8,12,9,5,0,6,2,4,3,1}, + {11,6,7,10,8,12,9,1,5,4,2,0,3}, + {8,12,7,10,9,11,6,2,0,4,3,5,1}, + {10,12,7,11,8,9,0,2,1,6,4,3,5}, + {12,9,11,10,7,8,4,3,1,0,6,2,5}, + {12,7,11,8,10,9,5,2,1,4,0,3,6}, + {12,8,10,7,11,9,0,3,6,2,4,5,1}, + {7,9,11,10,12,8,1,0,3,6,5,2,4}, + {9,6,11,7,10,8,12,3,1,0,2,5,4}, + {7,8,10,12,11,9,0,4,3,6,1,5,2}, + {10,8,7,9,11,6,12,5,3,0,1,4,2}, + {6,8,10,12,7,9,11,1,4,2,5,0,3}, + {6,12,7,11,10,9,8,2,4,5,0,3,1}, + {8,10,12,9,7,11,5,0,3,2,4,6,1}, + {6,8,10,7,11,9,12,3,1,5,2,4,0}, + {10,12,11,7,9,6,8,0,4,3,2,5,1}, + {7,10,8,12,9,11,6,0,2,4,1,3,5}, + {12,9,7,11,8,10,4,5,0,2,1,3,6}, + {8,7,9,12,6,11,10,1,0,2,4,5,3}, + {11,7,9,12,8,6,10,5,0,2,1,4,3}, + {9,7,10,12,6,8,11,1,0,2,5,3,4}, + {8,12,11,10,7,9,2,3,5,0,4,6,1}, + {11,9,8,7,10,6,12,5,4,0,2,1,3}, + {11,9,7,6,10,8,12,4,5,1,0,3,2}, + {12,7,11,8,10,6,9,1,0,2,3,5,4}, + {12,7,8,6,9,11,10,4,0,3,5,2,1}, + {11,7,10,6,12,9,8,4,5,0,3,2,1}, + {10,12,8,11,7,9,0,4,2,5,6,3,1}, + {8,7,9,12,10,6,11,1,0,5,2,4,3}, + {12,9,11,10,7,8,4,0,5,1,3,2,6}, + {7,10,8,9,11,6,12,0,2,1,4,3,5}, + {11,8,12,7,6,10,9,3,2,1,5,0,4}, + {9,12,11,8,7,10,6,4,3,0,5,2,1}, + {8,12,10,7,11,9,3,6,2,5,1,4,0}, + {7,8,11,10,9,12,5,1,4,0,3,6,2}, + {7,12,11,9,6,8,10,0,4,3,2,5,1}, + {11,7,6,10,8,12,9,4,1,3,2,5,0}, + {7,9,8,11,10,6,12,0,2,1,4,3,5}, + {7,11,6,10,12,8,9,0,4,1,5,3,2}, + {7,10,12,9,8,11,1,4,6,2,0,3,5}, + {9,12,7,11,8,10,3,1,5,0,2,6,4}, + {12,11,7,9,8,10,0,2,1,3,5,4,6}, + {9,6,8,12,11,7,10,4,0,2,1,5,3}, + {10,7,9,12,11,8,6,1,5,2,0,4,3}, + {9,12,11,8,10,7,6,2,1,0,4,3,5}, + {9,7,11,10,12,8,0,2,6,4,1,5,3}, + {7,11,8,10,9,12,3,5,2,1,6,4,0}, + {8,11,9,6,10,7,12,5,1,3,4,2,0}, + {8,12,10,11,7,9,3,6,5,0,2,4,1}, + {9,6,12,7,11,10,8,5,2,1,3,0,4}, + {11,10,9,7,12,8,2,5,3,6,1,4,0}, + {7,6,9,11,8,10,12,0,4,2,5,1,3}, + {6,9,11,7,12,8,10,2,4,5,3,1,0}, + } + }, + { 14, { + {9,8,11,10,12,7,13,6,4,3,1,5,2,0}, + {12,9,8,11,13,10,7,3,1,5,2,0,6,4}, + {13,10,12,8,7,9,11,6,4,1,0,5,2,3}, + {13,9,11,8,12,10,7,6,5,2,1,0,4,3}, + {11,7,10,13,9,8,12,1,3,2,4,0,6,5}, + {10,7,12,11,13,9,8,0,4,2,1,3,6,5}, + {9,12,8,11,13,10,7,0,6,4,3,1,5,2}, + {10,8,7,13,9,11,12,5,1,4,0,6,3,2}, + {8,10,13,12,11,7,9,2,0,5,1,4,3,6}, + {13,10,7,8,11,9,12,2,6,0,4,1,3,5}, + {11,7,9,12,8,13,10,5,6,2,0,4,1,3}, + {11,9,7,12,10,8,13,4,6,3,2,5,1,0}, + {12,8,13,11,9,7,10,4,5,2,1,3,6,0}, + {8,11,9,13,7,10,12,4,6,0,5,2,3,1}, + {8,7,10,12,11,9,13,6,3,5,2,4,0,1}, + {13,7,9,12,11,10,8,1,3,2,4,6,5,0}, + {8,11,10,12,9,7,13,6,2,1,3,4,0,5}, + {7,11,8,9,13,10,12,0,3,2,1,5,4,6}, + {9,8,13,10,12,11,7,2,6,5,0,3,1,4}, + {12,10,9,8,13,11,7,6,3,2,0,5,1,4}, + {8,13,7,12,11,10,9,2,4,1,5,0,3,6}, + {9,8,7,12,11,13,10,5,1,6,3,2,0,4}, + {13,10,8,12,7,11,9,4,0,3,6,2,1,5}, + {10,8,12,9,7,11,13,5,3,4,0,2,6,1}, + {9,8,7,10,13,12,11,4,6,1,5,0,3,2}, + {10,12,11,8,7,9,13,5,3,0,2,1,4,6}, + {11,13,7,9,8,12,10,5,1,0,4,6,2,3}, + {12,8,7,9,11,10,13,2,1,3,5,4,0,6}, + {12,11,13,10,7,9,8,4,1,0,5,2,3,6}, + {7,11,13,9,10,8,12,6,2,4,1,5,3,0}, + {9,11,7,13,8,12,10,3,4,6,0,2,5,1}, + {10,9,8,11,13,7,12,0,2,1,6,3,5,4}, + {9,13,10,12,8,7,11,6,4,1,3,5,0,2}, + {11,13,10,12,8,7,9,6,1,4,3,0,2,5}, + {11,13,9,8,7,10,12,6,3,2,5,0,4,1}, + {11,7,9,8,10,13,12,6,3,2,0,4,1,5}, + {13,10,7,9,11,8,12,2,5,1,3,4,6,0}, + {8,7,9,11,10,13,12,3,5,4,1,0,6,2}, + {12,11,7,13,10,9,8,6,5,1,2,4,3,0}, + {11,10,9,8,7,13,12,1,5,2,4,0,6,3}, + {8,11,10,13,9,12,7,0,3,1,5,2,6,4}, + {13,7,12,9,11,10,8,2,6,3,0,1,5,4}, + {11,8,12,10,9,7,13,5,1,4,0,3,2,6}, + {7,12,11,9,8,10,13,2,0,5,4,3,1,6}, + {10,8,11,7,13,12,9,1,3,5,0,2,4,6}, + {12,10,13,7,11,9,8,3,6,5,1,4,2,0}, + {13,12,11,9,8,7,10,3,2,6,5,4,1,0}, + {7,13,8,11,9,12,10,0,2,4,3,6,5,1}, + {9,11,13,10,12,7,8,3,1,4,6,0,5,2}, + {9,13,7,11,10,12,8,6,0,5,1,4,3,2}, + {9,12,7,8,11,10,13,6,2,0,5,4,1,3}, + {12,8,11,10,7,13,9,6,1,4,5,2,0,3}, + {12,10,11,13,8,7,9,4,3,1,0,2,6,5}, + {9,7,10,8,13,12,11,6,3,1,0,2,5,4}, + {10,13,9,7,12,8,11,4,3,2,0,1,6,5}, + {13,8,10,9,12,11,7,1,3,0,5,2,6,4}, + {12,11,10,7,13,9,8,1,0,3,5,2,6,4}, + {12,7,9,8,13,10,11,1,6,4,2,0,3,5}, + {8,13,9,7,11,10,12,3,4,2,5,0,6,1}, + {12,9,13,8,11,10,7,1,0,5,3,4,2,6}, + {12,9,7,10,8,13,11,4,3,1,2,6,5,0}, + {8,10,12,7,13,11,9,0,6,2,1,4,5,3}, + {12,11,10,8,13,9,7,6,1,0,2,5,4,3}, + {9,8,10,7,12,11,13,0,1,3,2,5,4,6}, + {9,13,11,10,7,8,12,3,1,4,6,2,0,5}, + {9,11,7,8,13,12,10,5,3,6,2,4,1,0}, + {13,9,12,8,11,10,7,1,0,5,6,3,2,4}, + {9,11,7,12,10,8,13,1,3,5,4,0,6,2}, + {10,7,11,13,9,12,8,1,2,4,0,6,5,3}, + {12,9,13,10,8,7,11,3,2,1,6,5,4,0}, + {11,9,10,13,8,7,12,0,6,1,3,5,4,2}, + {10,8,7,11,13,12,9,4,1,5,3,6,2,0}, + {7,10,13,8,11,9,12,0,6,3,5,1,4,2}, + {8,7,12,10,13,9,11,2,3,0,6,5,1,4}, + {12,8,10,7,11,9,13,4,3,5,1,6,0,2}, + {10,7,11,9,8,13,12,4,3,6,2,5,1,0}, + {12,9,10,8,11,13,7,0,6,4,2,1,5,3}, + {8,12,10,7,13,11,9,1,5,0,6,3,2,4}, + {7,13,8,11,9,12,10,1,5,4,2,0,6,3}, + {8,13,9,7,12,10,11,3,6,2,5,0,4,1}, + {13,11,8,10,7,9,12,0,3,4,2,1,6,5}, + {11,13,7,10,8,9,12,0,2,4,3,1,6,5}, + {7,12,11,8,10,9,13,6,1,3,4,2,5,0}, + {10,12,8,11,9,13,7,0,5,1,6,3,4,2}, + {11,9,7,13,8,12,10,5,4,0,2,1,3,6}, + {10,12,7,11,8,9,13,6,4,1,0,3,5,2}, + {8,12,10,9,7,11,13,5,3,1,4,0,6,2}, + {13,8,10,12,11,7,9,5,1,6,2,4,3,0}, + {12,8,10,13,9,11,7,3,2,4,0,6,1,5}, + {11,13,9,8,12,10,7,1,3,5,6,2,0,4}, + {7,11,12,10,8,13,9,0,6,1,4,3,2,5}, + {9,8,13,12,10,7,11,2,4,0,3,6,5,1}, + {11,7,9,13,12,10,8,4,6,1,3,2,5,0}, + {13,9,8,7,11,10,12,6,0,3,5,2,1,4}, + {10,12,9,13,8,11,7,5,4,2,1,3,0,6}, + {13,12,10,9,7,11,8,4,0,3,2,5,6,1}, + {7,13,8,11,10,9,12,5,1,4,3,0,6,2}, + {13,7,10,8,12,11,9,4,3,0,2,1,5,6}, + {10,7,9,8,13,12,11,6,3,1,4,2,5,0}, + {11,10,9,8,13,12,7,6,4,0,3,2,1,5}, + } + }, + { 15, { + {12,10,13,11,14,9,7,8,6,1,3,5,0,4,2}, + {13,11,10,12,14,7,9,8,1,4,2,0,6,3,5}, + {14,11,12,10,9,7,13,8,0,4,1,3,6,5,2}, + {14,7,10,13,11,8,12,9,6,2,5,1,3,0,4}, + {9,14,12,10,13,11,8,2,4,3,6,1,0,7,5}, + {8,9,14,12,11,13,10,0,2,6,1,4,7,5,3}, + {8,7,10,11,13,9,12,14,1,4,6,3,2,5,0}, + {8,7,10,12,14,13,9,11,6,3,5,0,2,1,4}, + {11,9,12,14,10,13,8,7,5,0,4,3,2,6,1}, + {14,10,7,9,8,11,13,12,1,4,2,5,6,3,0}, + {12,11,9,8,13,10,14,2,4,5,7,1,6,3,0}, + {13,10,12,7,11,14,9,8,1,6,3,5,0,4,2}, + {11,7,14,8,10,9,13,12,5,2,3,0,6,4,1}, + {10,12,9,8,14,11,13,7,3,2,1,5,0,6,4}, + {12,10,9,11,14,8,13,5,4,2,6,3,1,7,0}, + {14,10,9,13,12,8,11,2,5,1,3,6,4,0,7}, + {11,8,12,10,14,13,9,7,1,6,3,2,0,5,4}, + {13,8,12,10,14,9,11,1,5,3,7,6,2,4,0}, + {13,11,14,10,12,9,8,0,7,3,2,4,1,6,5}, + {13,12,7,10,14,9,11,8,5,4,6,3,1,0,2}, + {8,9,13,10,14,12,11,6,5,7,2,4,1,3,0}, + {8,11,10,12,9,13,14,4,1,6,3,7,2,0,5}, + {9,13,10,8,12,14,11,2,0,6,4,3,5,7,1}, + {11,10,12,9,14,13,8,0,3,7,4,5,1,6,2}, + {13,11,10,7,12,9,8,14,6,4,0,2,3,5,1}, + {12,14,10,8,9,13,11,1,7,4,2,6,0,3,5}, + {13,12,7,9,11,8,14,10,1,3,2,4,5,0,6}, + {14,10,12,11,9,13,8,1,4,6,3,2,0,7,5}, + {11,8,7,10,13,12,14,9,3,6,1,0,5,4,2}, + {14,12,9,7,8,11,10,13,5,0,6,1,4,3,2}, + {8,11,9,10,13,12,14,2,0,5,4,1,6,3,7}, + {12,7,10,13,9,11,8,14,2,0,4,3,6,1,5}, + {12,10,13,8,14,9,11,7,0,5,1,6,3,2,4}, + {10,8,14,12,9,11,13,0,5,4,7,1,3,6,2}, + {12,10,13,7,8,11,14,9,4,3,0,2,6,1,5}, + {12,11,8,10,14,13,9,3,1,5,0,4,6,2,7}, + {9,8,10,7,12,14,13,11,3,0,2,5,4,6,1}, + {8,12,11,14,9,13,10,7,0,4,6,2,1,5,3}, + {9,12,14,8,11,10,13,3,2,5,1,4,7,6,0}, + {11,14,9,8,13,12,7,10,0,4,1,3,6,2,5}, + {9,11,8,13,7,10,12,14,2,4,5,0,6,3,1}, + {10,7,9,13,12,8,14,11,0,3,2,1,5,4,6}, + {12,11,9,14,10,13,8,2,1,3,7,6,5,0,4}, + {11,12,7,13,9,8,10,14,1,0,2,6,3,5,4}, + {11,10,14,8,9,12,7,13,6,4,2,0,5,1,3}, + {10,14,8,11,13,9,12,0,2,6,5,3,1,4,7}, + {8,12,10,11,9,14,7,13,5,3,2,4,0,6,1}, + {9,7,12,11,10,13,8,14,1,3,5,2,0,4,6}, + {11,13,8,10,14,12,9,1,3,7,6,0,5,2,4}, + {10,14,13,12,9,8,11,7,1,4,0,3,6,2,5}, + {8,12,10,7,14,11,13,9,0,5,6,3,2,1,4}, + {7,10,12,13,8,14,9,11,4,6,1,0,5,3,2}, + {9,12,8,14,10,13,11,6,4,2,7,5,0,3,1}, + {11,10,12,9,13,8,14,6,3,1,4,0,7,2,5}, + {10,13,11,14,7,9,8,12,6,5,0,2,4,3,1}, + {9,12,14,8,13,10,7,11,3,2,5,0,1,6,4}, + {14,9,13,7,12,11,10,8,0,5,3,1,6,4,2}, + {11,13,12,9,14,7,10,8,1,3,0,6,4,5,2}, + {8,14,9,12,11,13,10,6,1,3,7,2,5,4,0}, + {8,12,11,13,10,9,14,2,5,7,3,0,4,6,1}, + {9,12,8,11,13,10,14,0,2,7,4,3,1,5,6}, + {13,9,11,7,8,10,12,14,2,4,1,6,5,0,3}, + {9,14,7,10,8,12,11,13,0,2,6,5,4,3,1}, + {11,14,8,12,9,7,13,10,2,4,0,1,6,3,5}, + {12,9,11,10,13,8,14,6,5,4,0,7,3,2,1}, + {13,10,8,11,12,14,7,9,4,0,3,2,6,5,1}, + {14,8,11,13,10,12,9,7,1,6,2,4,0,3,5}, + {9,8,14,13,10,12,11,6,3,5,7,4,2,1,0}, + {8,11,14,9,10,13,12,5,4,7,0,2,6,1,3}, + {12,10,13,8,11,7,14,9,3,5,1,2,4,0,6}, + {8,14,11,13,9,10,12,4,6,3,0,2,5,1,7}, + {14,8,12,9,13,7,11,10,0,3,6,1,5,4,2}, + {8,13,9,12,10,14,11,3,7,4,6,5,2,1,0}, + {12,7,13,9,14,10,8,11,6,1,3,5,0,4,2}, + {14,10,9,12,8,7,11,13,6,4,3,1,0,2,5}, + {13,8,11,9,12,10,7,14,5,3,1,6,0,2,4}, + {10,14,7,13,8,12,11,9,4,1,0,2,5,3,6}, + {14,12,10,9,7,11,13,8,6,1,4,3,0,2,5}, + {14,13,10,8,11,12,7,9,6,5,2,0,3,1,4}, + {10,13,12,9,14,8,11,3,5,4,7,6,0,2,1}, + {9,11,13,10,8,12,14,3,7,5,2,1,4,6,0}, + {8,12,11,14,10,9,13,0,5,3,2,1,6,4,7}, + {11,12,14,10,9,8,13,0,4,3,2,7,5,1,6}, + {11,13,12,14,10,9,8,5,1,6,3,0,4,7,2}, + {9,11,8,12,10,13,7,14,3,2,1,4,6,0,5}, + {9,14,8,13,10,12,11,5,1,7,3,6,2,0,4}, + {14,9,12,11,8,13,10,3,2,4,0,5,7,1,6}, + {7,11,13,12,9,14,10,8,1,4,6,2,0,3,5}, + {10,14,13,8,7,11,9,12,3,0,4,6,5,2,1}, + {13,10,12,11,9,8,14,7,1,4,6,5,2,0,3}, + {7,9,14,11,10,13,12,8,2,0,6,3,5,1,4}, + {10,8,14,9,7,11,13,12,1,3,0,4,2,5,6}, + {12,7,10,8,11,9,14,13,2,4,1,5,3,6,0}, + {12,9,13,8,10,14,11,0,3,7,4,1,6,2,5}, + {14,8,7,10,9,11,13,12,6,2,0,4,1,3,5}, + {9,8,10,12,11,14,7,13,2,4,6,1,3,0,5}, + {11,10,9,8,14,13,12,7,6,2,0,4,3,5,1}, + {9,14,8,10,11,13,12,0,7,1,6,3,5,2,4}, + {10,9,14,7,12,8,11,13,6,0,5,2,4,3,1}, + {14,8,12,11,13,9,7,10,6,2,0,4,1,5,3}, + } + }, + { 16, { + {14,13,15,11,10,9,12,8,7,3,5,4,6,1,0,2}, + {15,12,14,11,13,10,9,8,2,6,5,3,1,4,7,0}, + {11,10,15,13,12,14,8,9,4,7,6,2,1,5,3,0}, + {10,8,14,11,15,13,12,9,0,5,3,2,4,1,7,6}, + {13,15,9,11,8,12,10,14,3,5,7,4,6,1,0,2}, + {13,10,8,12,9,11,15,14,6,7,1,5,3,0,4,2}, + {13,8,11,12,15,14,10,9,0,7,5,2,1,3,6,4}, + {9,14,11,13,8,10,12,15,0,4,3,6,2,5,1,7}, + {8,12,9,15,14,11,13,10,0,5,1,3,7,4,2,6}, + {10,12,15,13,8,9,11,14,2,6,3,5,7,0,4,1}, + {9,14,12,13,11,10,15,8,4,7,5,0,3,2,1,6}, + {14,8,11,13,9,12,10,15,1,4,7,3,5,6,0,2}, + {14,13,10,15,11,9,12,8,6,2,5,4,7,1,0,3}, + {15,13,9,8,12,14,11,10,5,1,3,6,4,7,2,0}, + {11,15,8,9,13,10,12,14,5,4,0,6,2,1,3,7}, + {13,10,12,14,9,15,11,8,1,5,3,7,2,4,6,0}, + {14,13,12,11,15,9,8,10,3,5,7,6,4,1,0,2}, + {13,12,11,14,8,15,9,10,5,4,7,1,3,0,6,2}, + {10,13,11,8,12,14,9,15,3,6,7,4,2,0,5,1}, + {8,11,13,14,12,10,15,9,4,7,2,6,0,5,3,1}, + {13,11,9,14,8,15,10,12,0,7,3,5,4,2,6,1}, + {15,12,9,11,8,10,14,13,6,5,7,1,3,2,4,0}, + {9,10,8,12,11,13,15,14,0,5,7,4,2,1,6,3}, + {8,13,10,12,9,15,14,11,7,3,6,2,0,5,4,1}, + {10,9,12,15,13,11,14,8,2,5,4,6,3,7,1,0}, + {10,8,12,11,14,9,15,13,1,5,0,3,2,7,4,6}, + {10,14,11,8,15,12,9,13,7,0,3,2,6,5,1,4}, + {13,12,15,14,8,11,10,9,5,6,0,2,4,7,3,1}, + {9,10,12,14,11,13,15,8,7,4,1,3,2,5,0,6}, + {9,12,8,11,14,13,15,10,6,3,1,7,5,0,2,4}, + {15,13,10,9,14,12,11,8,7,3,2,5,4,0,1,6}, + {13,12,15,9,10,8,14,11,2,1,4,6,5,0,3,7}, + {13,9,11,15,8,10,12,14,7,4,0,2,6,5,1,3}, + {11,15,10,8,13,14,12,9,3,2,7,0,4,6,5,1}, + {8,10,14,12,9,11,15,13,5,2,4,0,3,6,1,7}, + {8,12,11,15,9,13,10,14,6,5,1,3,2,4,7,0}, + {13,12,8,15,10,11,14,9,5,7,1,3,0,4,2,6}, + {13,12,10,9,8,15,11,14,1,6,4,7,3,0,2,5}, + {12,14,11,8,10,9,13,15,0,2,4,5,3,6,1,7}, + {11,9,15,13,12,10,8,14,1,0,2,4,3,7,5,6}, + {15,13,8,14,10,12,9,11,2,4,6,7,1,3,5,0}, + {8,14,10,13,9,11,15,12,4,6,5,1,3,0,7,2}, + {11,10,12,9,8,13,15,14,4,5,7,3,6,2,1,0}, + {11,13,12,9,15,10,8,14,6,7,5,0,2,4,3,1}, + {10,14,12,8,11,13,15,9,6,1,0,5,7,4,2,3}, + {9,15,12,11,10,13,14,8,0,2,4,7,6,1,5,3}, + {14,10,8,15,13,9,11,12,0,7,1,6,4,2,5,3}, + {14,9,10,8,13,15,12,11,4,0,7,2,1,6,5,3}, + {10,8,13,9,14,12,15,11,3,1,4,2,5,0,6,7}, + {11,15,10,8,14,9,12,13,7,5,2,0,3,6,4,1}, + {10,15,14,9,13,12,11,8,3,5,6,2,1,4,0,7}, + {14,15,12,11,10,13,9,8,0,6,4,3,2,5,7,1}, + {9,14,13,12,8,11,10,15,0,2,4,1,3,7,6,5}, + {9,12,15,14,13,11,10,8,6,4,2,5,1,7,0,3}, + {14,10,9,11,12,15,13,8,7,6,3,0,5,1,4,2}, + {14,10,9,11,15,8,12,13,6,5,0,2,7,4,1,3}, + {8,10,12,9,14,13,11,15,6,2,0,7,4,5,1,3}, + {10,9,13,11,14,12,15,8,7,4,0,3,2,5,1,6}, + {12,15,14,11,9,10,13,8,2,6,3,5,0,4,7,1}, + {13,15,10,14,9,12,11,8,0,5,7,6,2,4,3,1}, + {15,14,10,12,8,11,9,13,0,1,4,6,5,3,2,7}, + {14,11,10,12,9,13,15,8,7,2,4,6,0,3,1,5}, + {14,9,15,13,10,12,8,11,3,5,4,7,1,6,0,2}, + {15,12,10,8,13,9,14,11,2,0,7,5,4,3,6,1}, + {9,14,8,13,11,15,12,10,4,7,3,0,5,1,6,2}, + {12,8,14,11,15,10,9,13,5,0,2,7,1,4,6,3}, + {12,9,14,11,8,13,15,10,3,2,6,4,7,1,5,0}, + {8,12,14,13,10,9,11,15,6,5,4,2,1,3,0,7}, + {9,14,12,11,10,15,13,8,7,4,6,0,2,1,5,3}, + {14,13,11,15,8,10,12,9,6,2,4,7,0,5,3,1}, + {11,10,8,13,15,12,9,14,0,7,6,4,5,3,2,1}, + {12,15,10,8,13,9,14,11,7,1,0,5,4,2,6,3}, + {9,8,12,10,14,11,15,13,4,3,1,7,6,0,2,5}, + {11,15,14,8,10,13,9,12,2,7,6,4,0,5,3,1}, + {9,12,14,11,15,8,10,13,1,4,3,5,7,6,2,0}, + {14,11,10,9,13,12,8,15,7,0,5,2,6,4,1,3}, + {13,9,11,14,10,15,12,8,1,5,2,4,0,3,7,6}, + {13,9,11,8,10,15,14,12,6,0,7,5,1,4,2,3}, + {11,14,10,15,12,9,8,13,3,5,4,0,2,6,7,1}, + {11,14,9,10,13,8,12,15,7,0,2,6,4,1,3,5}, + {8,15,9,12,11,13,10,14,0,7,6,1,3,5,4,2}, + {14,10,9,8,15,12,11,13,3,6,7,5,2,4,1,0}, + {8,11,10,15,13,12,9,14,4,2,1,7,3,0,6,5}, + {8,11,13,9,15,10,14,12,1,3,2,6,5,7,0,4}, + {11,8,15,14,12,10,9,13,2,4,3,0,5,1,7,6}, + {13,9,11,10,15,12,14,8,7,3,2,5,0,6,1,4}, + {10,14,9,12,8,13,11,15,7,0,6,1,3,5,4,2}, + {8,10,13,9,11,15,12,14,7,3,1,2,6,4,0,5}, + {8,10,15,12,14,9,13,11,6,4,5,7,3,2,1,0}, + {12,15,9,14,11,10,8,13,5,0,3,7,6,4,2,1}, + {14,10,12,15,8,13,9,11,4,6,1,0,2,5,7,3}, + {14,11,8,10,9,12,15,13,3,6,7,5,0,2,4,1}, + {9,12,15,13,11,8,14,10,4,3,6,1,7,0,5,2}, + {10,12,8,14,9,13,11,15,5,7,0,2,4,6,3,1}, + {9,13,11,10,15,14,8,12,7,1,3,0,5,2,4,6}, + {15,12,11,10,14,9,13,8,2,5,3,7,6,1,4,0}, + {8,11,15,12,9,13,10,14,5,4,1,7,3,6,2,0}, + {14,12,10,15,13,11,9,8,1,6,2,5,0,4,3,7}, + {8,13,9,11,15,14,10,12,3,1,4,6,0,2,7,5}, + {15,8,12,9,11,13,10,14,6,0,2,5,3,7,1,4}, + } + }, + { 17, { + {16,10,15,11,14,13,9,12,8,3,7,2,6,4,0,5,1}, + {9,13,10,12,14,16,8,15,11,1,4,7,6,5,3,2,0}, + {15,11,9,16,13,12,14,10,5,7,0,6,8,2,4,1,3}, + {14,13,11,16,10,9,12,15,3,2,8,5,7,1,0,4,6}, + {10,15,12,11,14,9,13,16,1,6,3,0,2,8,5,4,7}, + {8,12,11,14,16,10,9,13,15,6,1,3,2,0,5,4,7}, + {10,8,13,16,12,15,14,9,11,3,4,1,0,2,6,5,7}, + {8,16,12,15,9,13,11,10,14,7,3,5,1,4,6,2,0}, + {12,14,11,10,13,9,16,8,15,2,1,0,5,4,3,7,6}, + {16,10,13,8,15,12,11,9,14,7,2,0,3,6,4,1,5}, + {11,8,12,15,14,10,13,9,16,4,0,2,5,7,6,3,1}, + {15,14,10,16,9,12,13,11,6,0,5,7,2,8,1,4,3}, + {16,13,11,10,15,8,12,14,9,1,6,4,3,7,0,2,5}, + {11,13,14,16,8,10,15,12,9,3,2,5,7,1,6,4,0}, + {15,11,10,13,12,16,9,14,0,3,8,1,7,5,6,4,2}, + {13,12,14,16,11,15,10,9,7,6,1,8,3,2,5,0,4}, + {9,16,14,11,15,10,13,12,5,4,0,2,7,6,1,8,3}, + {15,12,9,11,14,8,16,10,13,1,0,3,6,5,7,4,2}, + {12,9,14,13,15,10,16,8,11,4,6,1,3,5,7,2,0}, + {16,9,8,14,13,12,15,10,11,6,2,5,3,0,7,1,4}, + {15,9,11,13,12,10,14,16,7,4,5,2,1,3,6,8,0}, + {15,11,14,12,10,16,9,13,4,0,3,7,5,8,1,6,2}, + {15,14,13,16,11,10,12,9,5,0,3,1,4,2,6,8,7}, + {10,13,15,14,9,11,8,12,16,4,2,5,3,1,0,7,6}, + {15,8,9,14,12,16,11,13,10,4,7,5,2,6,3,1,0}, + {10,13,11,14,16,12,9,15,7,3,6,5,4,0,8,2,1}, + {14,8,15,10,11,13,12,9,16,6,0,2,5,3,7,4,1}, + {15,14,12,16,13,11,10,9,8,5,1,7,2,4,6,0,3}, + {15,8,14,12,10,9,13,16,11,7,2,6,0,5,1,3,4}, + {13,12,14,16,10,9,11,15,7,5,1,4,0,2,6,3,8}, + {8,11,15,9,12,10,14,16,13,1,4,3,5,0,2,6,7}, + {13,10,16,11,15,12,14,9,8,7,1,3,5,0,2,4,6}, + {11,14,10,16,15,9,13,12,4,1,0,6,7,3,5,8,2}, + {12,15,13,10,14,11,9,16,2,1,3,5,4,7,6,8,0}, + {11,16,15,14,13,8,10,12,9,0,5,6,1,4,3,2,7}, + {14,11,15,13,10,16,12,9,3,7,2,4,6,8,0,1,5}, + {8,11,9,12,14,13,16,10,15,3,6,2,1,0,5,7,4}, + {9,12,16,15,13,8,14,11,10,7,4,3,2,0,5,1,6}, + {13,8,16,11,14,9,12,15,10,5,7,1,6,0,4,3,2}, + {15,14,16,13,9,11,12,10,2,4,0,5,8,6,1,3,7}, + {15,10,13,16,14,11,9,12,6,4,8,0,2,5,3,7,1}, + {15,11,14,9,16,12,10,13,4,8,6,0,5,3,2,7,1}, + {14,16,9,11,10,13,12,15,7,2,6,5,8,1,0,4,3}, + {9,15,10,13,12,16,14,11,6,5,8,1,2,0,4,7,3}, + {15,9,8,16,10,13,12,14,11,5,4,6,0,3,2,1,7}, + {15,8,13,12,14,9,16,11,10,2,0,6,3,1,4,7,5}, + {15,11,16,9,10,13,12,14,3,1,5,2,6,0,7,4,8}, + {10,13,16,15,14,9,11,12,5,3,7,4,6,1,0,8,2}, + {15,13,11,16,10,14,12,9,0,7,4,2,6,1,5,8,3}, + {8,15,13,10,12,9,14,11,16,5,3,7,0,2,4,1,6}, + {9,15,14,11,10,16,12,13,4,3,7,1,5,2,8,6,0}, + {15,12,10,16,11,14,13,9,3,2,4,8,0,6,1,5,7}, + {10,9,15,8,14,11,13,12,16,1,7,5,2,0,3,6,4}, + {16,15,14,10,12,8,9,13,11,3,5,2,1,7,6,4,0}, + {8,11,10,16,9,12,14,13,15,1,0,7,4,2,5,6,3}, + {16,12,14,8,11,15,10,13,9,1,3,0,4,6,2,7,5}, + {12,8,10,9,13,16,11,15,14,0,2,3,5,7,1,6,4}, + {11,8,14,10,16,13,15,9,12,7,3,0,6,5,4,2,1}, + {11,13,15,10,9,16,12,14,5,3,7,1,6,4,2,0,8}, + {10,13,12,15,11,8,14,9,16,0,4,7,2,1,6,3,5}, + {14,11,9,15,8,13,10,16,12,0,6,2,4,3,5,1,7}, + {8,13,11,15,12,10,9,16,14,1,3,2,0,6,4,7,5}, + {13,9,15,11,10,16,12,14,7,1,8,3,4,2,0,6,5}, + {16,14,10,9,12,8,11,15,13,0,6,2,1,3,7,5,4}, + {13,15,10,12,14,8,11,16,9,7,2,6,4,0,1,5,3}, + {12,10,16,8,14,11,13,9,15,7,1,5,3,6,4,0,2}, + {12,15,8,14,16,13,10,11,9,4,7,3,5,1,6,2,0}, + {12,14,16,11,15,13,10,9,8,6,0,4,7,5,1,3,2}, + {13,15,14,12,16,11,10,9,5,4,2,6,0,8,3,7,1}, + {15,12,14,13,9,11,16,10,4,8,0,5,2,7,1,6,3}, + {12,10,9,11,13,8,16,15,14,1,7,4,6,3,2,0,5}, + {10,15,13,16,8,14,12,9,11,0,2,6,4,3,7,1,5}, + {15,16,14,8,12,11,10,9,13,5,2,7,1,3,6,0,4}, + {13,15,11,16,10,9,12,14,5,2,7,1,0,4,6,3,8}, + {10,11,13,15,12,8,16,9,14,2,7,5,4,1,0,6,3}, + {16,15,12,11,10,8,13,9,14,1,5,4,3,2,6,0,7}, + {12,10,16,9,13,15,11,8,14,7,6,0,3,1,5,4,2}, + {12,11,10,9,13,15,14,8,16,6,0,2,7,4,1,5,3}, + {16,10,12,14,11,9,13,15,2,1,8,4,6,5,3,0,7}, + {16,8,12,15,9,10,14,11,13,6,2,0,5,4,3,1,7}, + {14,10,8,13,16,11,9,15,12,6,5,4,7,2,0,1,3}, + {13,12,15,11,8,14,10,16,9,0,2,1,5,7,3,6,4}, + {11,16,14,9,8,12,13,10,15,3,1,0,2,6,5,7,4}, + {9,13,16,14,10,8,15,12,11,1,4,3,0,7,5,6,2}, + {16,9,11,13,15,10,12,14,7,5,2,4,3,8,1,6,0}, + {12,16,14,9,10,13,11,15,3,5,1,0,2,7,4,8,6}, + {12,16,15,14,8,11,10,9,13,6,7,4,0,5,3,2,1}, + {13,11,14,16,10,12,9,15,7,6,3,0,2,1,5,4,8}, + {9,16,12,14,11,10,15,13,7,5,4,1,3,6,8,2,0}, + {16,11,14,9,8,13,10,12,15,3,6,7,1,5,2,4,0}, + {12,8,15,14,13,11,10,16,9,0,5,3,7,1,4,6,2}, + {13,10,16,8,9,11,15,14,12,6,1,4,7,5,3,0,2}, + {12,8,14,13,15,9,11,16,10,5,0,7,3,6,1,4,2}, + {11,15,9,13,10,8,16,12,14,3,5,0,4,2,7,1,6}, + {16,14,13,12,8,10,15,9,11,6,1,0,3,2,5,7,4}, + {16,11,10,13,9,12,15,14,0,4,2,7,6,1,3,8,5}, + {9,14,12,10,15,13,11,8,16,6,2,7,1,5,0,4,3}, + {11,16,10,15,13,9,14,8,12,7,4,1,6,2,0,3,5}, + {10,15,16,13,12,11,9,14,6,3,1,5,4,8,7,0,2}, + {13,11,10,14,16,9,12,15,3,2,7,1,8,6,0,5,4}, + } + }, + { 18, { + {17,14,16,15,9,10,13,12,11,5,4,2,7,6,0,3,8,1}, + {16,14,17,15,11,9,13,12,10,5,1,7,4,6,2,3,8,0}, + {12,9,16,14,13,17,10,15,11,1,6,0,8,5,3,7,2,4}, + {9,17,12,11,14,16,10,15,13,8,0,4,1,5,2,6,3,7}, + {10,15,13,12,14,9,17,11,16,8,0,3,7,5,4,1,2,6}, + {13,10,17,15,14,9,12,11,16,5,2,0,3,7,8,6,1,4}, + {13,10,17,12,9,15,14,16,11,1,0,5,2,7,6,8,4,3}, + {13,9,11,16,10,15,12,17,14,0,8,3,5,1,4,6,2,7}, + {14,12,16,11,10,15,13,9,17,2,4,8,6,7,3,5,1,0}, + {17,15,10,14,11,13,9,12,16,8,1,5,3,4,0,6,2,7}, + {17,16,14,9,11,13,10,15,12,7,0,2,8,5,4,3,1,6}, + {16,14,10,9,15,13,11,17,12,0,4,6,2,5,1,3,8,7}, + {16,13,11,10,9,15,12,17,14,5,2,7,6,1,4,0,8,3}, + {16,14,13,12,17,15,10,9,11,3,4,8,1,6,5,7,0,2}, + {13,17,12,15,9,16,10,11,14,0,4,3,2,6,8,5,7,1}, + {12,11,15,9,14,16,10,13,17,3,0,8,6,7,1,5,4,2}, + {15,12,16,10,13,17,11,14,9,0,5,1,6,8,3,2,7,4}, + {9,12,16,15,14,11,17,10,13,2,1,5,7,6,8,3,0,4}, + {9,11,15,16,13,10,17,12,14,8,7,5,1,3,0,2,6,4}, + {16,10,14,13,15,9,11,17,12,1,4,2,7,8,5,0,3,6}, + {14,16,11,17,12,15,9,13,10,2,3,8,4,1,0,7,6,5}, + {12,16,13,11,10,15,17,14,9,1,6,5,4,8,3,0,7,2}, + {17,13,14,16,10,12,11,15,9,8,6,0,7,2,5,4,1,3}, + {13,9,14,11,17,16,15,10,12,4,7,3,1,0,2,6,8,5}, + {11,9,12,10,14,17,13,15,16,7,3,2,8,5,1,0,6,4}, + {14,16,15,12,13,11,10,9,17,6,8,5,0,4,7,2,1,3}, + {13,15,17,16,12,14,9,11,10,7,8,6,0,5,3,2,4,1}, + {10,13,16,11,9,12,14,17,15,6,4,1,8,3,0,2,5,7}, + {11,15,13,9,12,10,17,16,14,0,4,8,7,5,3,2,1,6}, + {15,17,14,12,16,11,13,10,9,3,5,0,2,6,1,8,7,4}, + {15,17,9,13,10,12,11,14,16,8,2,7,6,5,1,4,0,3}, + {12,14,11,10,9,16,15,13,17,8,1,5,4,7,3,6,2,0}, + {10,14,16,15,17,13,9,12,11,0,5,2,1,3,7,6,8,4}, + {12,14,13,11,16,10,15,9,17,5,4,7,3,8,1,0,2,6}, + {12,9,16,17,15,11,10,14,13,8,3,0,5,2,1,4,7,6}, + {11,15,9,17,12,10,16,14,13,6,0,4,1,3,7,2,8,5}, + {12,15,17,14,11,13,10,16,9,8,6,4,2,5,3,1,7,0}, + {12,14,11,16,9,10,13,15,17,7,5,2,0,4,1,6,3,8}, + {14,17,16,13,11,10,12,15,9,7,8,1,4,3,0,2,6,5}, + {10,14,17,15,12,9,16,11,13,0,8,1,3,7,2,6,4,5}, + {16,11,13,9,12,14,10,15,17,5,2,4,8,6,1,3,7,0}, + {9,13,12,14,17,10,16,11,15,6,5,2,8,1,3,7,0,4}, + {17,10,15,11,14,12,16,9,13,7,6,8,1,3,5,4,2,0}, + {16,11,13,12,14,9,17,15,10,7,1,8,3,2,4,5,0,6}, + {12,9,14,10,16,11,17,13,15,2,7,0,3,6,4,8,1,5}, + {16,11,9,13,10,14,17,15,12,5,8,4,0,7,1,3,6,2}, + {11,9,15,12,10,16,13,14,17,8,2,0,6,3,5,4,1,7}, + {11,17,14,13,10,16,12,15,9,7,4,0,3,1,6,8,2,5}, + {12,11,14,16,10,17,15,13,9,7,4,8,3,2,0,1,6,5}, + {16,15,17,9,10,13,12,14,11,1,4,8,6,5,7,3,0,2}, + {16,13,15,11,17,12,14,10,9,8,4,6,3,5,7,1,0,2}, + {10,15,17,14,16,12,11,13,9,4,6,0,3,5,1,8,7,2}, + {17,11,15,10,14,13,16,9,12,8,3,5,2,7,0,6,1,4}, + {12,14,10,13,11,16,15,9,17,8,3,6,1,7,5,2,4,0}, + {12,13,15,17,10,9,16,11,14,0,4,3,2,8,5,7,1,6}, + {14,13,11,15,17,9,16,10,12,7,0,2,4,3,5,8,6,1}, + {13,11,15,12,10,14,9,17,16,4,6,2,8,0,5,7,1,3}, + {10,13,11,15,12,14,17,16,9,1,8,5,4,0,3,2,7,6}, + {17,12,14,13,10,9,15,11,16,8,1,4,7,0,2,6,3,5}, + {14,16,13,17,15,12,11,10,9,2,4,6,3,0,8,5,1,7}, + {13,15,12,9,17,11,16,14,10,5,2,4,3,7,8,1,0,6}, + {14,17,13,10,15,12,11,16,9,7,5,0,3,1,6,4,8,2}, + {9,17,12,10,15,13,14,16,11,0,7,6,2,5,8,3,1,4}, + {9,11,15,12,16,14,17,10,13,4,7,2,0,5,1,3,6,8}, + {11,15,17,14,10,12,13,9,16,3,1,0,2,8,7,4,6,5}, + {14,10,16,9,13,17,11,15,12,5,4,1,0,3,8,7,6,2}, + {13,11,16,10,17,15,14,12,9,0,6,1,7,4,3,2,5,8}, + {10,11,15,12,9,16,13,17,14,7,4,1,3,6,8,2,0,5}, + {12,10,9,16,14,17,13,11,15,3,1,6,4,0,7,5,2,8}, + {16,12,17,10,15,14,11,13,9,0,5,2,7,6,8,1,4,3}, + {17,11,15,10,16,14,13,12,9,2,4,6,8,7,3,5,0,1}, + {13,17,10,15,9,14,11,16,12,5,6,3,0,4,1,7,2,8}, + {17,9,15,14,16,11,13,10,12,7,2,5,0,8,1,6,4,3}, + {10,17,9,12,11,14,16,13,15,2,6,4,8,5,3,1,7,0}, + {12,16,11,14,10,9,17,15,13,2,4,8,5,7,1,0,3,6}, + {9,12,10,14,11,13,16,15,17,1,5,6,0,8,3,7,4,2}, + {9,10,17,16,13,12,14,11,15,7,5,2,1,0,8,3,6,4}, + {14,16,12,11,17,9,15,13,10,7,1,3,6,8,2,5,0,4}, + {11,15,9,17,10,14,13,12,16,6,4,3,7,2,5,1,8,0}, + {16,13,9,10,12,17,11,15,14,6,0,8,4,1,7,5,3,2}, + {16,14,17,13,12,11,10,15,9,4,7,2,5,1,8,0,3,6}, + {17,14,11,13,10,16,15,12,9,1,8,2,7,4,3,6,5,0}, + {14,12,15,9,17,11,16,10,13,3,2,8,0,6,4,7,5,1}, + {17,15,14,13,12,11,16,10,9,6,0,2,1,3,8,5,7,4}, + {12,9,17,16,10,14,13,15,11,8,4,1,5,6,0,7,3,2}, + {15,12,16,14,10,13,17,9,11,1,4,3,0,5,8,6,7,2}, + {14,11,16,12,10,15,13,17,9,0,8,3,5,1,7,4,2,6}, + {15,12,11,16,13,17,10,14,9,5,6,3,0,8,2,4,1,7}, + {11,12,16,14,10,17,15,13,9,4,8,5,7,1,3,6,2,0}, + {16,14,11,13,17,12,15,10,9,5,4,3,1,7,0,2,6,8}, + {16,13,15,17,14,12,11,10,9,4,3,0,2,8,1,5,7,6}, + {16,15,12,10,14,13,11,17,9,2,3,6,8,5,4,1,0,7}, + {17,12,10,16,15,9,11,14,13,0,7,4,2,1,8,3,6,5}, + {13,15,10,11,17,16,12,14,9,2,4,6,8,1,0,5,7,3}, + {17,12,10,13,11,15,14,9,16,7,1,3,0,6,8,4,2,5}, + {10,9,13,15,11,16,17,12,14,6,2,8,7,3,1,0,5,4}, + {11,14,13,17,10,12,15,9,16,7,4,8,5,3,1,6,2,0}, + {14,12,10,16,11,15,13,17,9,4,6,0,5,2,8,1,7,3}, + {15,14,16,13,9,11,12,10,17,0,6,1,8,3,5,4,2,7}, + {12,16,14,13,17,11,9,10,15,8,5,2,6,4,1,0,7,3}, + } + }, + { 19, { + {18,13,17,11,10,16,15,14,12,1,3,7,4,9,0,6,2,8,5}, + {9,17,10,14,13,12,11,16,15,18,6,3,1,5,2,8,7,0,4}, + {10,17,11,15,14,13,12,16,9,18,2,7,6,1,4,3,0,5,8}, + {18,16,11,14,17,10,12,15,13,4,8,6,3,5,0,7,2,9,1}, + {17,10,15,14,16,13,9,12,18,11,6,1,7,3,0,2,4,8,5}, + {18,13,10,17,12,15,11,14,16,1,5,9,0,4,3,7,2,8,6}, + {12,10,16,15,14,17,13,18,11,1,3,0,4,8,2,5,9,7,6}, + {13,12,18,11,14,17,15,9,16,10,1,0,2,4,7,5,3,6,8}, + {14,9,10,17,15,11,13,16,12,18,4,2,1,6,5,7,0,8,3}, + {16,14,12,18,9,13,15,11,17,10,5,8,6,0,3,7,2,4,1}, + {14,12,10,18,11,16,15,17,13,9,0,6,3,8,5,4,2,7,1}, + {17,13,18,14,11,15,9,12,10,16,8,3,5,7,6,1,0,4,2}, + {17,16,14,9,13,11,18,10,12,15,7,6,0,1,3,8,4,2,5}, + {16,14,13,11,18,10,12,15,17,8,4,0,7,2,1,6,9,3,5}, + {13,11,18,12,15,10,9,17,14,16,1,4,7,3,5,8,0,2,6}, + {12,10,13,15,18,14,9,17,16,11,8,3,6,2,1,7,5,4,0}, + {18,15,11,12,17,14,13,10,16,1,7,4,3,6,5,0,8,2,9}, + {14,12,11,15,18,10,17,16,13,2,4,9,1,8,6,7,5,0,3}, + {11,17,14,13,15,10,18,12,16,6,2,8,4,7,3,0,5,9,1}, + {16,14,18,17,12,10,11,15,13,3,7,6,2,5,9,4,0,8,1}, + {11,14,18,15,13,12,10,17,16,0,2,5,3,9,1,7,8,4,6}, + {12,18,14,16,11,13,10,17,9,15,7,4,0,3,2,1,6,5,8}, + {15,14,16,13,18,17,12,9,11,10,1,0,2,4,6,5,7,3,8}, + {10,16,14,17,13,11,18,12,15,3,9,4,8,0,2,5,1,7,6}, + {15,9,14,16,10,18,17,13,12,11,0,7,4,2,6,8,1,3,5}, + {10,16,13,14,11,18,15,17,12,5,3,7,9,2,4,1,8,6,0}, + {11,10,16,14,12,9,13,17,15,18,1,8,4,6,3,7,5,0,2}, + {13,10,14,18,15,12,17,16,11,9,1,8,6,0,2,4,7,5,3}, + {16,18,10,12,9,11,14,13,15,17,7,0,4,2,1,5,8,3,6}, + {14,10,12,11,16,9,15,13,18,17,4,3,5,2,0,6,1,7,8}, + {10,15,11,17,16,12,18,14,13,3,6,2,0,8,7,4,1,9,5}, + {14,10,12,17,15,11,13,18,9,16,2,4,3,6,8,0,5,1,7}, + {15,9,13,18,11,16,12,17,10,14,4,7,5,8,1,0,2,6,3}, + {10,13,18,11,14,16,15,12,17,9,6,3,2,7,4,1,5,8,0}, + {12,11,16,15,17,13,18,14,10,3,5,1,4,6,7,9,2,0,8}, + {16,12,11,15,13,18,17,14,10,4,3,1,7,8,6,9,0,2,5}, + {13,10,12,9,17,14,16,15,18,11,2,8,4,0,5,3,6,7,1}, + {15,10,13,18,11,14,17,12,16,5,7,4,9,1,3,2,6,8,0}, + {15,17,16,10,14,13,11,18,12,2,8,0,4,3,5,1,6,9,7}, + {15,13,12,17,10,14,16,11,18,5,2,6,1,7,3,8,0,9,4}, + {11,18,12,10,17,16,15,13,14,4,3,0,2,6,8,7,5,9,1}, + {9,11,16,15,12,14,10,13,18,17,1,3,2,0,5,7,4,6,8}, + {15,10,13,11,17,9,12,18,16,14,1,3,6,4,0,5,8,2,7}, + {13,18,17,12,10,14,16,11,15,1,4,6,3,0,5,2,8,7,9}, + {12,18,14,17,13,11,16,9,10,15,5,0,8,4,2,7,6,3,1}, + {16,14,13,17,12,15,11,10,18,1,7,0,4,2,9,5,6,3,8}, + {14,16,12,17,10,9,15,13,18,11,4,8,2,7,3,6,1,5,0}, + {18,17,15,12,16,11,13,14,10,6,2,7,3,9,5,1,4,8,0}, + {15,9,18,10,17,12,16,14,13,11,1,6,4,0,5,3,8,2,7}, + {13,18,15,17,14,12,11,10,16,2,9,4,7,1,6,8,5,0,3}, + {16,18,17,10,12,15,13,11,14,5,9,1,6,2,7,3,0,4,8}, + {14,17,15,10,13,16,9,11,12,18,8,0,5,4,7,3,2,6,1}, + {18,15,14,11,13,17,12,10,16,3,1,9,6,4,2,7,8,5,0}, + {10,14,17,16,13,9,15,11,18,12,8,2,0,6,5,4,3,7,1}, + {14,10,13,17,12,11,15,9,18,16,3,7,6,8,4,0,5,2,1}, + {11,17,16,10,12,14,18,15,9,13,4,8,6,3,5,0,2,1,7}, + {14,11,17,15,18,13,10,12,16,8,7,1,6,0,4,9,3,5,2}, + {16,17,13,12,11,14,18,15,10,2,8,6,0,9,1,3,7,5,4}, + {12,11,17,16,18,15,14,13,10,4,7,1,0,6,5,3,2,9,8}, + {16,11,18,9,17,12,14,13,10,15,8,6,5,0,4,7,3,2,1}, + {13,12,16,15,9,18,17,10,14,11,7,4,1,0,8,3,2,6,5}, + {10,14,18,17,13,15,11,12,16,2,8,1,4,9,6,3,5,0,7}, + {16,12,18,10,14,17,15,13,11,6,0,8,2,4,5,9,3,7,1}, + {12,17,18,16,13,10,14,11,15,0,3,7,5,8,2,6,4,1,9}, + {17,15,16,14,13,12,11,10,18,0,2,8,3,7,6,9,4,1,5}, + {15,17,12,14,11,13,18,16,10,4,7,9,3,2,6,8,5,1,0}, + {18,17,11,16,10,15,13,12,14,5,4,0,7,3,2,9,6,1,8}, + {12,18,10,15,14,17,16,13,11,2,4,6,5,7,0,3,9,1,8}, + {16,10,9,13,15,12,11,17,14,18,8,6,7,3,5,0,2,1,4}, + {10,15,17,12,18,11,13,16,14,7,4,6,3,0,2,9,8,5,1}, + {16,11,13,17,10,18,14,15,12,1,8,0,5,2,4,6,3,9,7}, + {13,16,18,14,12,9,10,17,11,15,6,3,5,1,8,2,4,0,7}, + {17,15,18,16,11,9,12,14,13,10,1,6,8,2,5,4,3,0,7}, + {9,17,16,13,15,18,11,14,10,12,2,5,3,7,4,6,1,8,0}, + {16,13,12,10,18,9,11,17,15,14,0,6,4,1,3,8,5,7,2}, + {17,12,11,13,15,14,10,16,18,9,2,1,8,4,6,5,3,7,0}, + {12,18,13,17,16,15,14,10,11,0,9,7,6,3,2,4,1,8,5}, + {17,15,13,12,10,14,16,11,18,8,1,6,4,7,9,3,2,0,5}, + {16,18,12,9,11,17,10,14,13,15,4,0,5,1,3,7,8,2,6}, + {12,18,17,10,13,16,15,14,11,4,2,1,7,9,6,5,3,0,8}, + {17,16,9,12,18,15,10,13,11,14,2,5,8,4,7,1,3,6,0}, + {13,10,12,15,17,11,16,18,14,6,4,5,2,0,8,3,9,1,7}, + {10,11,18,15,14,16,12,17,13,1,6,2,0,3,8,7,9,5,4}, + {12,11,16,14,18,15,17,13,10,3,4,6,2,5,7,0,9,8,1}, + {13,17,11,18,14,16,12,10,15,5,8,1,7,9,2,6,4,3,0}, + {13,17,10,11,14,16,9,12,15,18,3,8,0,7,6,5,2,1,4}, + {14,16,15,12,11,18,13,10,17,9,7,4,2,6,0,3,1,8,5}, + {14,17,16,12,11,9,13,10,15,18,7,4,6,3,2,8,5,1,0}, + {16,12,17,15,10,18,11,14,13,8,5,9,4,0,7,3,6,2,1}, + {10,15,12,18,17,16,11,14,13,6,4,9,8,3,1,0,2,5,7}, + {15,18,12,11,14,13,9,17,16,10,3,0,1,6,2,4,7,5,8}, + {18,12,11,15,17,16,14,10,13,5,7,2,6,9,4,3,8,1,0}, + {12,9,14,16,15,18,11,17,10,13,4,2,0,7,5,3,6,8,1}, + {18,16,12,17,14,13,10,15,9,11,8,2,6,1,7,0,5,4,3}, + {9,12,16,13,18,15,10,17,14,11,2,3,8,0,6,5,4,7,1}, + {15,12,16,14,11,18,13,17,10,2,1,4,3,6,9,7,5,8,0}, + {9,13,15,16,10,17,12,18,11,14,4,2,0,5,7,1,3,8,6}, + {16,10,12,17,14,18,11,13,15,6,0,7,2,1,4,8,9,5,3}, + {11,14,16,9,13,18,12,10,15,17,3,8,6,4,1,0,2,7,5}, + {16,14,12,18,11,13,15,17,10,5,8,1,9,2,7,0,6,4,3}, + } + }, + { 20, { + {12,16,15,19,13,17,14,11,18,10,1,5,0,6,2,8,7,9,4,3}, + {14,13,12,19,11,15,17,16,10,18,7,3,1,6,5,8,4,9,2,0}, + {18,11,17,12,19,14,15,13,16,10,7,6,0,4,9,1,3,8,2,5}, + {11,13,10,12,15,19,14,17,16,18,5,3,1,8,7,4,9,2,0,6}, + {18,10,13,12,15,17,19,11,14,16,5,8,2,4,3,7,1,6,0,9}, + {18,15,12,16,13,19,14,17,11,10,8,4,6,7,9,1,0,5,3,2}, + {18,10,17,11,13,15,12,19,14,16,6,3,1,5,8,4,0,2,9,7}, + {11,14,16,13,19,18,17,12,15,10,8,1,9,5,4,0,2,7,3,6}, + {10,17,13,18,14,12,16,11,19,15,8,5,2,6,3,1,0,4,9,7}, + {11,13,17,15,12,14,19,18,10,16,8,3,9,4,6,5,0,2,7,1}, + {12,10,11,16,18,14,17,15,13,19,4,0,8,7,3,9,2,1,6,5}, + {11,14,12,10,16,18,15,19,13,17,5,7,0,9,2,6,1,8,4,3}, + {12,19,13,18,14,15,11,16,10,17,9,1,4,8,5,7,6,3,0,2}, + {12,18,11,14,13,15,10,17,19,16,6,2,4,3,7,5,9,0,1,8}, + {16,19,14,10,15,11,18,17,12,13,1,7,6,9,2,4,0,5,3,8}, + {10,15,17,19,16,14,12,13,18,11,8,1,0,3,5,9,7,4,6,2}, + {13,18,15,10,14,16,12,11,19,17,5,7,2,8,0,6,3,9,4,1}, + {19,13,12,10,18,17,16,11,15,14,9,5,8,1,3,4,7,6,2,0}, + {13,18,11,16,14,12,19,17,15,10,9,8,2,6,4,3,0,5,1,7}, + {17,10,16,14,11,13,19,12,15,18,6,2,0,3,1,9,5,8,4,7}, + {16,15,19,12,14,10,18,13,11,17,9,7,3,1,0,4,6,2,5,8}, + {16,19,11,14,18,13,15,10,17,12,9,2,3,5,7,6,1,8,0,4}, + {16,12,18,13,10,14,19,17,11,15,7,2,6,3,0,5,8,9,1,4}, + {12,18,19,16,13,10,15,17,14,11,0,9,7,4,8,6,3,5,2,1}, + {11,14,17,18,13,10,16,12,15,19,5,8,7,4,3,6,0,2,9,1}, + {12,15,17,16,10,13,11,19,18,14,0,3,7,5,8,1,9,2,6,4}, + {11,13,19,16,14,17,15,12,18,10,8,1,4,2,6,3,5,9,0,7}, + {11,10,17,14,12,19,16,18,13,15,5,6,1,8,3,2,4,7,0,9}, + {15,12,14,11,18,13,19,16,17,10,3,9,7,6,0,5,1,4,2,8}, + {15,12,14,17,19,11,10,18,13,16,3,7,6,4,1,0,2,9,5,8}, + {13,18,17,12,19,14,16,15,11,10,9,8,3,0,5,7,6,2,1,4}, + {16,17,15,19,12,11,14,13,10,18,4,8,6,2,1,5,0,3,9,7}, + {11,19,17,14,18,13,16,15,10,12,8,0,7,5,4,1,3,9,6,2}, + {15,18,11,19,12,17,10,16,14,13,5,1,4,3,2,8,0,7,9,6}, + {10,13,17,15,11,14,18,16,19,12,6,8,7,9,4,3,1,2,5,0}, + {18,16,13,19,15,17,10,12,14,11,6,9,7,4,1,8,2,5,0,3}, + {19,12,15,17,11,18,10,13,16,14,1,4,9,5,8,2,0,3,7,6}, + {10,13,16,12,14,17,19,15,11,18,5,9,8,4,3,7,1,6,0,2}, + {15,14,11,18,16,13,12,19,10,17,7,2,8,3,0,4,9,5,1,6}, + {13,18,17,15,19,10,12,11,14,16,5,7,3,0,2,8,9,6,1,4}, + {12,16,14,18,13,15,11,19,10,17,3,2,1,7,9,5,4,6,8,0}, + {16,14,10,12,18,13,17,15,11,19,8,1,4,2,7,3,6,5,9,0}, + {16,15,10,14,12,19,11,18,13,17,3,6,4,7,1,9,0,2,8,5}, + {10,14,13,11,17,16,19,15,12,18,9,5,2,1,0,4,8,3,6,7}, + {14,17,11,10,13,16,12,19,18,15,4,7,6,1,9,0,2,8,3,5}, + {11,16,19,18,12,17,15,14,13,10,7,6,5,8,1,3,0,4,9,2}, + {17,13,10,15,18,11,12,14,16,19,0,7,1,6,4,3,8,5,2,9}, + {14,18,15,11,10,19,13,12,17,16,8,4,1,6,5,2,9,0,3,7}, + {14,18,11,17,13,19,10,12,16,15,6,3,8,0,4,2,9,5,7,1}, + {18,17,11,14,13,19,16,10,15,12,8,6,4,0,2,1,5,3,7,9}, + {17,15,14,19,10,11,18,13,16,12,9,7,6,4,0,8,5,1,3,2}, + {10,12,11,18,16,13,14,17,19,15,3,9,1,5,0,2,8,7,4,6}, + {13,18,16,19,12,15,17,11,14,10,7,2,3,1,0,5,9,6,8,4}, + {15,17,11,19,10,16,13,12,18,14,0,2,4,6,9,7,8,1,5,3}, + {17,16,13,15,19,12,14,11,18,10,2,5,9,3,0,8,7,6,1,4}, + {12,16,13,17,14,11,19,10,15,18,4,6,0,2,1,5,9,7,3,8}, + {19,14,17,13,16,18,15,10,12,11,9,3,1,4,7,6,2,5,8,0}, + {18,14,13,19,10,12,15,11,16,17,5,7,4,1,6,2,8,3,0,9}, + {12,17,19,13,15,14,11,10,18,16,9,4,8,3,5,1,7,0,6,2}, + {13,14,17,16,18,10,15,12,19,11,0,9,4,8,1,6,3,5,7,2}, + {11,14,12,10,16,18,17,19,15,13,7,9,3,2,0,8,4,6,1,5}, + {17,13,19,12,15,11,16,10,18,14,1,8,2,0,7,6,5,4,9,3}, + {19,15,10,13,16,18,17,14,12,11,0,9,8,1,5,4,2,7,6,3}, + {10,16,14,13,12,15,18,11,17,19,0,7,1,3,2,5,9,8,6,4}, + {17,13,12,19,16,11,15,14,10,18,8,5,2,9,7,6,4,0,3,1}, + {19,14,12,17,15,13,16,18,11,10,0,3,5,9,7,1,8,6,4,2}, + {15,17,11,14,13,16,18,12,19,10,9,2,7,1,3,5,0,8,6,4}, + {18,11,17,10,16,14,13,12,15,19,5,1,7,2,9,4,6,0,8,3}, + {13,16,15,18,11,17,19,14,12,10,9,4,3,2,7,6,1,0,5,8}, + {15,10,17,16,13,18,14,12,19,11,5,7,1,0,9,3,2,6,4,8}, + {16,15,12,19,10,18,17,13,11,14,4,7,2,6,8,1,5,3,0,9}, + {14,12,11,16,10,13,18,15,19,17,0,2,4,6,5,8,7,1,9,3}, + {17,15,13,18,10,19,12,16,11,14,0,2,8,1,9,6,5,4,3,7}, + {10,18,14,11,16,19,13,15,17,12,0,3,9,1,6,7,4,8,2,5}, + {14,17,11,18,16,13,10,15,12,19,1,4,2,5,7,8,3,9,6,0}, + {15,12,17,16,10,18,19,14,11,13,9,1,4,7,6,0,5,3,2,8}, + {10,12,19,17,11,16,15,13,18,14,8,1,3,2,7,6,4,0,5,9}, + {19,13,16,10,17,11,12,15,14,18,3,9,5,0,8,7,4,1,6,2}, + {10,18,14,17,11,16,13,12,19,15,5,9,6,1,3,8,4,2,7,0}, + {19,15,13,11,18,12,10,17,14,16,2,8,0,9,3,1,6,5,4,7}, + {12,18,16,14,19,11,13,15,17,10,8,6,2,9,3,5,4,1,0,7}, + {19,16,14,13,18,15,10,12,17,11,8,3,1,5,0,7,2,4,6,9}, + {15,19,18,11,14,13,10,17,16,12,2,7,9,0,6,8,5,4,3,1}, + {15,17,11,16,19,10,13,12,14,18,5,2,9,4,8,0,7,3,6,1}, + {17,19,18,10,12,14,11,16,15,13,4,9,0,6,2,7,1,3,5,8}, + {17,12,18,14,19,16,15,11,10,13,1,3,2,9,4,7,0,6,8,5}, + {11,17,13,19,18,10,12,16,14,15,5,0,9,6,4,7,2,1,8,3}, + {19,15,18,10,14,16,12,17,11,13,9,2,4,3,6,0,8,1,7,5}, + {19,12,18,13,10,17,15,11,16,14,1,8,9,5,0,2,4,7,3,6}, + {12,10,13,11,14,16,18,17,19,15,8,5,0,2,7,1,9,6,4,3}, + {16,15,12,17,13,11,19,14,10,18,3,7,4,6,1,9,0,8,5,2}, + {11,19,17,16,18,13,10,15,12,14,0,2,4,7,1,3,6,9,5,8}, + {10,16,14,17,13,15,12,11,19,18,8,1,4,7,3,0,5,2,6,9}, + {16,15,14,18,17,10,12,19,13,11,8,2,1,6,4,7,0,3,9,5}, + {12,15,13,16,19,17,14,11,18,10,2,7,0,5,1,4,6,9,8,3}, + {10,15,17,19,13,12,16,18,14,11,9,2,7,5,1,3,8,4,0,6}, + {17,14,18,13,10,16,11,15,19,12,4,6,1,0,7,3,9,8,5,2}, + {18,12,19,11,15,13,16,14,17,10,1,3,8,6,0,7,4,2,9,5}, + {15,12,16,10,18,11,13,19,14,17,8,3,0,4,1,7,6,2,5,9}, + {11,19,10,13,16,12,14,17,15,18,9,2,8,3,7,1,4,0,6,5}, + } + }, + { 21, { + {20,18,12,19,17,10,14,13,16,15,11,8,3,2,0,5,7,4,6,9,1}, + {18,16,13,12,14,17,15,20,19,11,6,0,9,5,3,10,1,4,8,2,7}, + {17,13,18,16,14,11,19,15,12,20,6,10,2,9,7,0,4,8,5,3,1}, + {18,14,12,17,19,16,13,20,15,11,9,8,5,3,1,10,2,6,4,0,7}, + {20,16,13,19,17,11,12,14,18,15,8,5,10,2,7,9,1,4,6,3,0}, + {18,10,17,19,13,14,20,15,12,11,16,6,2,4,3,9,1,8,0,7,5}, + {19,12,20,11,17,16,15,14,18,13,8,10,9,7,5,2,1,0,6,4,3}, + {14,11,10,17,13,15,19,16,18,20,12,7,3,1,6,9,0,5,2,4,8}, + {18,20,12,16,13,10,15,11,17,14,19,1,2,4,9,0,8,6,5,3,7}, + {19,12,18,16,13,14,11,20,17,15,1,6,10,0,5,9,3,8,2,4,7}, + {13,15,10,17,16,20,11,14,18,12,19,5,9,6,0,7,4,3,2,8,1}, + {16,11,19,10,13,20,14,18,17,15,12,8,0,9,6,2,4,3,5,7,1}, + {12,18,11,16,19,14,13,15,10,20,17,5,8,6,0,3,2,1,4,7,9}, + {16,18,17,19,14,15,12,20,11,13,7,6,2,5,9,0,8,4,1,3,10}, + {11,13,19,15,16,20,18,17,14,12,7,4,9,1,6,0,2,5,8,10,3}, + {18,11,15,13,16,14,19,17,10,12,20,5,2,4,7,0,9,8,6,3,1}, + {11,19,18,15,10,16,13,14,20,17,12,0,3,6,4,7,5,9,2,1,8}, + {15,12,17,11,14,19,13,18,10,20,16,9,7,6,2,5,8,4,3,1,0}, + {19,10,13,15,11,20,16,18,14,17,12,2,8,3,6,0,9,5,4,1,7}, + {19,16,14,17,11,10,20,12,18,13,15,2,8,9,3,5,1,4,7,0,6}, + {10,17,13,11,20,14,18,16,19,15,12,2,7,4,3,5,8,1,6,9,0}, + {13,12,15,11,16,19,18,14,17,20,10,7,1,0,4,9,3,8,6,5,2}, + {12,15,18,17,11,20,19,16,14,13,6,2,5,3,9,7,4,1,0,10,8}, + {17,10,14,19,12,15,20,16,18,11,13,4,8,2,9,0,5,3,7,1,6}, + {13,17,20,14,18,16,10,19,12,11,15,3,1,8,0,5,7,9,2,4,6}, + {15,12,17,19,18,13,20,16,11,14,2,10,0,5,8,7,3,4,6,1,9}, + {17,15,13,20,19,12,16,18,14,11,3,2,5,0,8,1,4,10,7,6,9}, + {16,18,17,20,11,15,13,19,14,12,2,10,6,0,3,9,1,5,8,4,7}, + {15,20,17,14,12,19,11,13,16,18,0,4,9,8,1,10,7,5,3,2,6}, + {15,12,17,14,19,20,11,18,16,13,3,6,1,8,10,2,4,7,0,9,5}, + {13,11,14,16,18,17,20,15,12,19,7,4,0,2,10,8,9,5,1,3,6}, + {19,12,14,13,17,10,20,15,18,16,11,8,4,7,6,9,3,2,5,1,0}, + {16,18,20,19,13,17,11,14,12,15,10,6,8,4,7,9,2,5,1,3,0}, + {20,11,17,19,18,13,15,14,12,10,16,6,0,8,2,1,5,7,4,3,9}, + {13,16,14,12,11,17,20,19,15,18,4,9,0,10,3,8,7,2,6,1,5}, + {15,20,12,17,18,10,14,11,13,19,16,4,8,0,6,2,1,3,7,5,9}, + {16,15,17,19,12,18,11,14,13,20,0,3,6,8,7,1,10,2,4,9,5}, + {17,15,11,18,13,16,12,20,14,19,2,0,8,1,6,4,5,10,3,9,7}, + {11,13,17,20,12,15,14,18,16,19,3,6,4,8,9,1,7,0,5,2,10}, + {18,14,11,13,16,12,15,17,20,19,7,5,10,4,3,6,8,2,0,9,1}, + {11,17,14,16,12,15,20,19,13,18,7,10,9,2,8,5,4,1,6,3,0}, + {17,12,15,19,10,11,13,18,20,16,14,5,7,0,9,2,4,8,6,1,3}, + {16,13,17,11,12,15,10,14,19,18,20,6,1,9,0,2,5,3,8,7,4}, + {14,11,16,20,13,18,15,19,17,10,12,6,3,4,0,7,2,8,5,1,9}, + {18,14,11,13,15,19,16,20,17,12,7,1,4,10,5,0,3,6,2,9,8}, + {19,17,16,11,18,13,20,15,12,14,10,3,8,1,9,7,2,5,4,0,6}, + {12,14,17,11,20,18,13,16,15,19,9,1,5,4,6,8,10,3,0,2,7}, + {18,14,12,20,13,19,17,15,11,16,2,8,7,10,5,4,3,6,0,1,9}, + {19,12,20,18,15,17,13,10,16,14,11,1,3,9,2,4,6,8,5,0,7}, + {17,15,14,10,20,18,12,11,13,16,19,0,4,1,8,6,3,5,7,2,9}, + {20,13,19,11,14,16,12,18,15,17,5,10,7,2,1,9,3,0,4,8,6}, + {17,20,11,14,16,10,15,18,12,19,13,4,3,5,8,6,7,0,2,9,1}, + {20,19,12,17,13,15,16,11,18,14,9,8,2,4,10,5,7,6,3,1,0}, + {12,15,18,10,19,13,17,16,14,11,20,8,4,6,1,2,7,5,9,3,0}, + {16,17,11,19,12,14,20,13,18,15,8,1,7,0,5,4,2,6,10,3,9}, + {19,12,11,20,17,14,16,18,15,13,3,6,1,9,5,4,8,7,2,0,10}, + {11,20,13,16,15,19,14,12,18,17,4,3,7,6,9,2,5,10,8,0,1}, + {11,12,19,15,14,17,13,20,18,16,8,6,9,3,5,10,2,0,4,7,1}, + {14,18,15,19,16,20,11,12,17,13,5,0,3,6,9,1,8,4,7,2,10}, + {14,13,17,15,12,18,11,20,16,19,5,2,1,4,7,3,8,0,10,9,6}, + {16,19,18,11,15,12,13,17,14,20,9,5,4,1,8,7,3,10,2,6,0}, + {13,16,18,14,17,19,12,15,11,20,9,8,6,0,10,7,1,4,2,5,3}, + {18,11,19,13,15,14,16,10,17,20,12,7,3,2,8,0,9,4,6,5,1}, + {16,14,17,11,20,12,15,19,18,13,9,7,3,5,10,8,6,2,1,0,4}, + {17,19,11,15,14,12,20,13,16,18,7,9,10,0,3,2,4,6,8,5,1}, + {12,19,15,18,10,17,16,14,20,13,11,4,7,9,3,8,1,6,2,0,5}, + {18,12,15,14,11,16,19,13,20,10,17,1,5,7,3,9,6,4,0,2,8}, + {19,11,16,14,17,20,12,18,13,15,6,9,5,3,8,7,2,4,1,0,10}, + {20,13,18,11,14,17,16,19,15,12,4,0,7,8,10,6,5,3,1,9,2}, + {13,16,11,15,20,17,19,14,12,18,9,7,2,5,3,1,10,0,6,8,4}, + {16,11,15,10,19,12,18,14,13,20,17,8,4,6,2,0,7,5,1,9,3}, + {12,19,18,10,13,16,11,14,17,20,15,8,1,5,3,6,7,4,2,9,0}, + {14,10,19,12,20,11,18,16,15,13,17,1,3,9,0,5,7,8,6,2,4}, + {19,18,12,16,15,17,13,11,14,20,1,7,4,6,8,2,5,3,10,0,9}, + {14,20,16,18,13,17,19,11,15,12,4,10,9,6,5,7,2,0,3,1,8}, + {18,11,12,20,16,15,19,17,14,13,0,5,2,7,4,1,8,3,9,6,10}, + {13,10,17,20,18,15,12,14,11,16,19,1,5,0,2,8,3,6,9,4,7}, + {19,13,20,11,12,15,18,17,14,16,9,6,4,3,5,8,1,10,0,7,2}, + {13,15,18,14,20,11,19,10,17,16,12,3,7,0,6,1,9,5,4,8,2}, + {12,11,15,17,13,20,19,18,16,14,9,0,6,2,5,8,10,4,1,3,7}, + {17,20,11,19,16,13,12,15,14,18,8,10,9,7,1,0,3,6,5,2,4}, + {14,20,19,18,17,13,15,11,16,12,5,2,0,3,8,6,1,7,9,4,10}, + {17,19,15,13,18,20,12,11,14,16,2,0,6,3,8,10,9,7,4,1,5}, + {20,16,10,19,18,14,12,15,17,13,11,6,0,4,1,2,5,9,3,8,7}, + {17,10,12,16,19,18,20,15,14,13,11,7,9,1,3,0,4,8,5,2,6}, + {14,11,15,18,17,13,12,19,16,20,10,0,3,1,4,8,5,7,6,2,9}, + {20,19,13,17,16,15,18,12,11,14,0,6,4,2,1,9,5,8,3,7,10}, + {20,18,16,13,19,15,11,17,14,10,12,6,4,9,2,7,1,5,0,8,3}, + {13,20,16,11,17,15,10,19,14,18,12,0,5,2,7,6,8,1,4,3,9}, + {20,11,15,17,19,16,18,13,12,14,7,1,5,3,9,2,10,8,6,4,0}, + {16,14,19,18,13,12,17,11,15,20,8,4,1,10,6,5,3,2,0,9,7}, + {18,12,20,13,19,16,11,15,14,17,1,0,7,4,6,10,8,3,5,9,2}, + {15,11,17,19,12,14,18,20,16,13,2,8,7,1,10,0,5,3,6,4,9}, + {16,12,19,18,20,17,15,14,13,11,4,7,5,8,1,2,0,9,3,10,6}, + {19,13,18,16,14,20,15,12,11,17,3,2,4,10,6,5,0,9,8,1,7}, + {11,19,14,10,12,17,13,15,20,16,18,7,4,5,8,0,9,1,3,6,2}, + {12,11,14,16,20,13,18,17,19,15,4,1,0,5,6,9,3,7,2,8,10}, + {19,12,17,14,11,16,18,15,20,13,8,3,1,10,5,0,2,9,6,4,7}, + {15,11,16,18,12,19,13,10,20,14,17,5,2,6,0,7,3,9,8,4,1}, + {17,15,19,14,18,20,11,16,13,10,12,9,4,7,2,1,8,0,3,5,6}, + } + }, + { 22, { + {21,13,18,19,11,20,17,15,14,12,16,7,9,2,0,6,4,10,5,3,1,8}, + {12,15,16,18,17,14,19,13,21,11,20,9,7,10,4,1,6,5,8,2,0,3}, + {16,13,20,11,18,14,12,21,17,19,15,3,5,2,1,9,7,0,6,4,10,8}, + {16,19,15,12,17,21,11,13,20,14,18,10,2,4,0,7,6,5,8,3,1,9}, + {19,12,14,13,18,21,11,17,20,15,16,5,8,4,3,1,0,9,7,2,10,6}, + {20,14,12,15,13,16,11,17,19,21,18,8,1,7,4,6,0,2,9,10,5,3}, + {19,18,11,13,16,21,14,12,15,20,17,10,1,4,6,8,3,5,7,0,2,9}, + {16,11,21,17,12,18,13,19,14,20,15,10,2,8,6,1,4,3,5,9,0,7}, + {14,21,18,20,12,19,16,15,11,17,13,4,7,3,9,10,2,5,1,8,6,0}, + {18,12,19,15,13,16,21,14,17,20,11,3,2,9,4,7,8,10,5,1,6,0}, + {20,14,12,19,18,11,15,21,17,13,16,4,3,5,1,6,10,2,7,9,8,0}, + {15,12,19,14,17,11,16,13,21,18,20,6,4,9,7,0,3,2,5,10,1,8}, + {21,20,12,16,19,17,13,18,11,15,14,6,0,7,10,1,3,2,5,9,4,8}, + {17,20,18,12,16,13,14,21,15,11,19,3,7,10,2,5,9,0,6,4,8,1}, + {14,13,12,19,17,21,15,18,11,16,20,8,5,6,4,3,2,1,10,7,0,9}, + {20,19,21,12,18,16,15,14,17,13,11,9,1,8,5,7,4,3,10,6,0,2}, + {20,15,19,11,18,17,13,12,21,16,14,10,8,1,7,4,9,6,2,0,5,3}, + {13,19,15,14,18,20,12,17,11,21,16,8,5,0,4,2,6,1,9,10,7,3}, + {15,12,17,19,13,21,16,18,14,20,11,6,9,5,2,0,4,3,8,7,1,10}, + {20,18,21,11,15,17,14,13,12,16,19,9,2,5,1,8,3,7,0,6,4,10}, + {17,19,18,15,21,14,16,12,13,20,11,3,10,9,4,7,1,5,8,0,2,6}, + {19,14,16,13,15,18,21,20,12,17,11,3,7,5,8,4,10,0,2,9,1,6}, + {17,21,18,12,19,15,13,16,20,14,11,10,8,4,3,7,5,1,0,6,2,9}, + {13,12,18,20,17,15,19,16,14,21,11,6,9,1,8,2,0,5,4,10,3,7}, + {17,19,13,12,11,16,15,21,20,14,18,10,3,1,7,6,8,0,4,5,2,9}, + {11,13,20,15,19,21,14,17,16,18,12,3,10,4,6,8,2,5,1,9,0,7}, + {19,18,20,16,11,13,15,17,21,14,12,8,7,5,1,10,2,0,4,3,6,9}, + {11,15,16,21,17,14,20,12,19,18,13,8,7,1,9,3,2,6,0,5,4,10}, + {13,15,19,16,18,21,12,14,20,11,17,8,10,3,7,4,2,6,9,0,5,1}, + {19,21,18,15,13,12,16,20,17,11,14,0,4,3,7,6,9,8,2,5,10,1}, + {16,11,15,17,12,20,18,14,13,21,19,0,5,9,6,10,1,3,7,4,2,8}, + {15,21,12,13,17,16,19,18,14,11,20,2,0,10,6,9,7,3,5,4,8,1}, + {17,15,11,18,14,16,13,20,12,19,21,2,0,6,1,5,4,8,3,9,7,10}, + {20,16,21,17,12,11,14,19,15,13,18,3,6,9,1,10,8,4,0,5,7,2}, + {13,19,21,15,18,17,16,11,12,20,14,4,1,0,6,3,10,5,7,9,8,2}, + {18,14,19,13,20,15,17,12,21,16,11,6,7,4,9,2,1,10,0,3,5,8}, + {17,16,18,20,12,14,21,13,15,19,11,10,3,7,1,8,0,9,2,4,6,5}, + {19,21,13,18,12,20,14,16,15,17,11,0,8,6,9,3,2,4,1,5,10,7}, + {19,13,18,11,20,15,21,17,12,16,14,3,8,1,10,7,2,5,9,0,4,6}, + {13,19,16,11,21,18,14,12,15,20,17,1,9,3,6,4,10,7,0,8,5,2}, + {21,19,15,12,17,20,14,18,16,11,13,4,0,7,1,6,5,3,10,8,2,9}, + {17,13,15,18,21,20,19,16,12,14,11,4,1,9,8,10,7,3,6,5,0,2}, + {19,13,16,11,20,12,17,15,18,21,14,0,5,6,10,7,2,1,3,9,4,8}, + {17,20,19,12,15,21,14,13,18,16,11,4,10,6,8,2,5,3,0,7,9,1}, + {18,21,17,16,19,13,12,11,14,20,15,10,3,5,1,7,9,0,2,8,6,4}, + {18,15,20,12,11,17,13,19,16,14,21,10,5,1,8,7,4,2,0,6,9,3}, + {15,19,12,17,11,16,18,14,21,13,20,8,7,5,2,0,3,6,1,9,10,4}, + {16,18,15,21,19,17,13,11,12,14,20,2,6,5,0,4,9,1,3,8,10,7}, + {18,20,14,17,12,16,13,19,21,15,11,8,4,7,5,2,0,6,10,1,3,9}, + {11,19,18,20,12,17,21,13,15,14,16,9,3,0,10,4,7,6,2,5,8,1}, + {21,20,17,13,16,11,14,19,12,15,18,10,8,7,2,4,3,6,5,1,9,0}, + {11,18,13,20,17,15,21,14,19,12,16,4,0,9,6,10,7,2,5,8,3,1}, + {18,20,13,16,11,19,12,17,21,15,14,2,7,4,6,10,0,8,5,3,1,9}, + {20,18,13,15,14,16,12,11,21,17,19,6,2,1,4,3,5,9,7,10,0,8}, + {19,17,15,11,13,14,21,16,18,12,20,10,7,3,8,6,1,5,9,0,4,2}, + {19,17,15,12,21,11,14,16,13,20,18,1,3,2,6,8,0,7,9,10,5,4}, + {12,16,20,13,15,17,11,14,21,19,18,6,10,3,1,7,4,9,2,0,5,8}, + {11,16,12,21,13,17,15,19,18,14,20,0,6,4,1,3,5,10,8,2,7,9}, + {19,13,16,15,17,21,14,18,12,11,20,2,3,1,6,10,0,5,8,4,9,7}, + {20,17,12,11,16,18,19,14,21,15,13,10,7,3,2,9,4,1,0,6,5,8}, + {18,13,19,11,17,12,14,20,16,15,21,2,6,4,8,7,5,9,3,10,1,0}, + {14,18,15,12,16,19,13,17,21,20,11,6,10,8,7,5,9,3,1,0,4,2}, + {12,19,17,21,18,11,14,16,15,13,20,6,0,10,4,2,7,1,9,5,3,8}, + {21,19,14,20,12,16,18,15,17,13,11,10,5,1,0,3,7,9,2,8,6,4}, + {16,12,11,15,17,18,20,13,19,21,14,3,10,5,8,7,0,4,2,9,6,1}, + {21,17,11,16,19,14,13,18,12,20,15,7,4,9,3,8,5,0,2,10,1,6}, + {12,21,20,16,13,11,19,15,17,14,18,5,8,1,2,7,3,9,4,6,0,10}, + {11,15,13,19,21,12,17,20,14,18,16,0,3,10,6,4,1,5,9,8,7,2}, + {13,15,11,17,12,14,20,18,16,21,19,4,7,9,0,8,3,6,2,10,5,1}, + {17,16,12,19,21,18,20,13,15,14,11,7,3,9,2,6,0,5,4,8,10,1}, + {18,17,11,13,16,12,20,14,21,15,19,10,5,7,4,9,3,0,2,6,1,8}, + {12,18,15,11,19,21,14,13,20,17,16,5,0,9,4,1,7,10,8,3,2,6}, + {12,18,11,13,16,20,17,19,15,14,21,10,9,8,5,7,4,1,3,2,0,6}, + {11,14,21,17,19,13,16,20,15,12,18,7,2,4,0,5,3,8,6,1,10,9}, + {16,12,15,20,18,17,19,14,13,11,21,3,10,9,5,0,6,4,8,7,2,1}, + {12,16,19,21,18,14,15,13,11,20,17,8,5,2,1,6,9,0,7,10,4,3}, + {15,19,18,14,13,16,12,21,17,20,11,3,2,4,9,5,7,1,0,8,10,6}, + {20,11,18,21,12,15,14,16,19,13,17,9,8,2,0,5,4,7,1,3,10,6}, + {21,19,13,17,16,15,11,18,12,20,14,4,8,2,10,5,6,3,7,0,9,1}, + {12,17,21,15,18,14,16,13,20,11,19,1,6,3,8,5,2,7,4,0,10,9}, + {14,20,13,18,16,11,19,15,12,17,21,5,2,7,6,1,8,4,3,0,10,9}, + {11,14,16,21,13,20,19,15,18,17,12,5,8,10,9,7,4,3,0,2,1,6}, + {13,19,18,21,17,14,16,15,12,20,11,3,6,0,9,4,8,7,2,1,5,10}, + {21,20,18,14,16,11,19,13,15,17,12,3,10,7,5,1,4,9,2,6,8,0}, + {12,14,13,18,17,15,20,11,19,21,16,7,0,5,9,8,10,2,1,4,6,3}, + {15,14,20,12,21,11,18,16,13,17,19,9,0,6,10,8,5,7,1,3,4,2}, + {17,12,18,19,14,21,20,13,11,16,15,9,1,7,6,10,5,0,2,4,3,8}, + {17,16,20,11,13,18,14,19,15,12,21,9,2,6,1,0,7,8,3,10,5,4}, + {19,15,12,14,16,18,20,17,11,13,21,9,6,4,7,0,3,5,1,8,10,2}, + {18,12,14,11,15,17,20,13,19,21,16,8,9,7,4,1,10,0,2,5,3,6}, + {15,13,17,14,16,19,21,11,12,20,18,0,8,1,3,9,6,2,10,5,7,4}, + {20,15,18,14,13,17,21,16,12,11,19,5,8,6,9,4,10,7,2,1,0,3}, + {14,11,21,16,12,17,13,15,18,20,19,2,8,6,0,7,4,1,3,9,5,10}, + {18,14,19,12,21,20,17,15,13,16,11,7,10,6,1,4,0,2,9,3,5,8}, + {11,13,21,14,17,19,16,15,20,12,18,10,2,5,3,8,6,9,7,1,0,4}, + {12,18,14,21,20,15,17,16,19,13,11,10,9,1,8,7,6,4,2,5,3,0}, + {11,13,20,17,19,21,16,15,12,14,18,2,5,4,7,9,0,8,3,10,6,1}, + {20,17,19,15,18,16,21,14,11,13,12,10,8,0,5,2,7,1,4,9,6,3}, + {16,21,13,11,15,20,14,18,17,19,12,3,7,2,9,1,4,8,10,6,5,0}, + {19,13,21,16,15,18,17,20,12,14,11,9,7,4,3,6,8,0,5,2,1,10}, + } + }, + { 23, { + {22,13,19,21,16,20,18,17,14,12,15,10,4,2,0,9,8,11,6,5,1,7,3}, + {22,20,13,11,19,21,18,15,17,12,16,14,6,8,0,2,5,1,9,4,7,3,10}, + {20,16,13,19,18,12,14,11,15,21,17,22,4,9,10,6,1,0,2,7,5,8,3}, + {12,15,20,13,17,16,14,21,19,22,18,9,0,8,7,10,4,1,3,5,11,6,2}, + {21,20,18,13,16,22,19,12,14,11,17,15,5,4,10,7,3,1,8,0,2,9,6}, + {22,12,21,14,18,16,20,13,11,17,15,19,4,7,5,3,10,1,0,2,6,9,8}, + {21,15,20,12,19,18,16,22,17,14,13,7,0,8,4,1,6,2,5,3,10,9,11}, + {13,16,18,19,22,17,21,11,15,12,14,20,0,9,5,10,7,6,4,8,1,3,2}, + {15,17,22,14,19,12,16,21,13,11,20,18,8,7,4,9,5,3,1,6,2,0,10}, + {16,15,19,21,20,22,12,14,13,18,17,5,11,7,10,6,3,1,8,0,9,4,2}, + {12,18,22,19,14,17,21,20,16,13,11,15,0,4,2,7,10,8,3,5,9,6,1}, + {18,14,17,12,22,13,19,21,20,15,11,16,10,3,2,7,9,5,1,6,8,4,0}, + {18,12,20,17,14,21,16,22,19,15,13,0,11,9,6,8,1,5,10,7,2,4,3}, + {11,15,19,16,12,21,13,18,14,22,20,17,2,9,8,10,6,0,7,4,5,1,3}, + {15,17,14,20,19,12,22,13,16,21,18,4,8,2,5,7,9,1,3,11,10,0,6}, + {22,19,12,21,18,16,17,14,13,20,15,1,10,8,7,5,2,6,9,11,4,3,0}, + {20,18,12,15,22,13,19,16,21,14,17,6,8,5,9,7,3,2,4,10,0,11,1}, + {12,11,21,16,18,15,19,17,22,20,14,13,10,3,2,9,1,5,4,0,7,6,8}, + {14,16,12,18,11,20,15,19,21,13,22,17,5,7,0,9,2,10,1,6,3,8,4}, + {20,15,14,22,21,17,19,12,11,16,13,18,2,10,8,1,7,4,9,5,0,6,3}, + {18,16,20,22,15,14,11,12,19,13,21,17,8,7,5,4,3,2,1,10,6,9,0}, + {12,17,21,14,16,18,15,13,22,19,11,20,0,9,3,8,7,1,6,10,4,5,2}, + {13,15,22,12,17,19,14,18,21,20,16,3,2,1,8,11,6,10,4,0,5,7,9}, + {18,12,21,11,15,20,13,22,14,16,19,17,8,10,7,4,1,3,0,6,5,2,9}, + {11,22,21,16,19,14,20,18,17,12,15,13,9,4,7,5,2,6,1,10,3,8,0}, + {19,20,11,22,17,15,18,16,14,12,21,13,9,2,8,7,1,4,6,0,5,10,3}, + {15,12,18,17,19,16,22,20,14,13,21,3,8,5,7,2,10,11,6,4,1,9,0}, + {12,15,18,14,17,21,13,20,19,16,22,1,3,8,11,0,4,10,6,9,2,5,7}, + {15,13,18,20,19,22,16,12,21,17,14,10,4,2,8,3,7,11,0,6,9,5,1}, + {18,14,19,20,16,11,13,22,17,12,21,15,4,6,10,5,2,8,0,9,3,1,7}, + {12,20,14,18,13,15,17,11,22,19,21,16,0,7,2,5,3,6,4,9,1,10,8}, + {20,11,19,16,14,13,17,21,18,12,22,15,0,4,3,10,9,7,6,2,5,8,1}, + {15,20,17,21,13,19,12,14,22,16,18,8,4,1,3,6,2,7,9,0,11,5,10}, + {14,18,21,12,15,11,16,22,17,19,13,20,3,0,9,7,6,8,4,2,5,10,1}, + {22,18,16,15,19,21,13,17,14,20,12,3,1,6,2,10,4,9,8,5,11,7,0}, + {11,19,15,17,13,22,16,12,20,18,21,14,2,4,6,1,5,3,8,9,7,0,10}, + {18,13,21,12,20,16,19,15,14,17,22,11,5,1,0,10,9,3,8,6,4,2,7}, + {13,20,22,16,15,19,14,12,21,18,17,8,7,0,1,6,3,10,9,5,4,2,11}, + {16,14,22,21,19,11,15,17,12,13,20,18,1,3,7,10,0,4,2,8,5,9,6}, + {19,20,16,12,15,14,13,18,21,17,22,10,6,9,8,3,2,5,7,4,11,1,0}, + {11,17,22,15,21,16,20,18,14,13,12,19,5,3,2,6,1,4,10,0,8,7,9}, + {20,12,16,22,19,14,17,15,13,18,21,1,5,0,4,10,3,7,9,11,6,8,2}, + {12,15,14,13,18,17,20,21,16,22,19,9,3,5,2,1,6,11,4,0,8,10,7}, + {14,19,22,20,16,12,17,21,13,15,18,5,7,11,9,8,1,3,6,2,10,4,0}, + {15,14,12,21,16,20,18,17,13,19,22,1,9,8,11,0,4,3,6,5,2,7,10}, + {18,14,17,13,19,21,20,12,22,11,16,15,1,8,6,2,9,7,5,4,10,3,0}, + {11,22,20,14,16,15,17,19,21,13,18,12,4,10,9,3,2,1,8,0,7,6,5}, + {19,21,12,14,16,18,13,17,20,22,15,9,6,11,4,2,1,10,8,7,0,5,3}, + {17,21,20,11,19,13,18,16,22,14,12,15,4,2,8,3,7,6,10,9,5,0,1}, + {13,19,14,21,12,17,22,15,20,18,16,5,4,10,11,7,6,2,9,1,0,3,8}, + {17,20,18,22,16,11,12,19,13,15,14,21,3,9,2,7,1,8,5,4,6,10,0}, + {17,16,21,14,22,19,18,12,15,13,20,6,11,7,10,9,1,3,0,2,5,8,4}, + {21,20,13,12,17,16,19,22,14,11,15,18,5,9,6,7,0,3,10,4,2,8,1}, + {17,16,12,18,14,21,13,15,22,20,19,9,11,10,7,4,0,2,8,6,5,1,3}, + {20,18,22,14,11,16,12,15,19,17,13,21,6,0,9,8,10,7,5,3,1,4,2}, + {17,13,20,18,19,21,12,15,11,14,22,16,8,1,3,6,2,0,9,4,7,5,10}, + {19,15,17,16,13,20,18,21,14,22,12,7,11,6,3,5,8,4,1,0,10,9,2}, + {15,13,21,11,17,19,18,20,22,14,16,12,8,9,5,0,6,4,3,1,10,7,2}, + {22,18,20,14,19,17,21,16,15,12,13,3,9,2,11,8,7,5,1,4,0,6,10}, + {21,18,13,11,17,20,14,22,16,15,19,12,10,5,1,3,2,4,6,0,7,9,8}, + {15,21,20,22,17,12,18,16,14,13,19,10,11,5,0,7,2,6,4,8,3,1,9}, + {20,15,18,13,12,22,14,17,19,16,11,21,4,3,1,6,9,8,0,7,2,10,5}, + {12,22,17,16,15,21,18,20,14,13,19,11,0,2,6,4,3,9,8,1,7,5,10}, + {21,20,22,15,19,12,14,18,13,16,11,17,5,3,7,0,8,6,10,9,1,4,2}, + {16,18,12,19,15,22,17,21,13,20,11,14,6,1,0,9,8,10,4,2,7,3,5}, + {22,20,16,14,13,18,21,19,12,17,15,4,10,9,0,8,6,3,1,7,5,2,11}, + {17,20,22,21,15,16,12,14,19,13,18,6,8,11,7,9,5,0,2,4,3,1,10}, + {19,18,14,22,21,12,20,16,13,17,15,1,8,7,4,10,2,6,11,0,9,5,3}, + {18,14,19,22,17,20,15,21,12,16,13,5,8,7,6,0,11,4,2,10,9,3,1}, + {18,17,14,13,12,19,16,15,22,21,20,7,11,5,1,0,10,4,6,2,9,8,3}, + {15,18,11,19,12,17,16,13,21,20,14,22,3,1,10,4,6,5,0,9,7,2,8}, + {19,17,12,22,13,21,20,14,11,16,15,18,8,1,4,0,5,7,9,2,6,3,10}, + {17,15,20,11,21,14,12,19,18,13,16,22,6,0,7,8,10,9,1,5,2,4,3}, + {21,15,22,16,14,20,17,19,12,18,13,5,1,11,6,4,2,0,8,3,9,7,10}, + {16,19,12,18,22,20,15,21,14,13,17,4,11,6,3,9,0,2,1,7,5,10,8}, + {13,21,20,22,14,11,16,19,18,15,17,12,1,8,7,5,3,2,4,6,10,9,0}, + {22,15,17,12,20,16,19,21,14,18,13,9,3,10,8,1,5,4,11,6,2,0,7}, + {16,15,11,18,20,12,13,22,17,19,21,14,4,3,2,6,5,8,1,9,7,10,0}, + {12,17,13,19,16,14,21,20,15,22,18,10,9,2,6,5,0,8,3,1,4,7,11}, + {16,12,15,13,21,20,19,22,18,14,17,1,3,2,7,9,5,8,11,0,4,10,6}, + {17,11,22,20,12,21,14,16,13,15,18,19,5,7,1,0,2,8,3,9,4,10,6}, + {22,14,12,18,16,13,15,17,20,19,21,2,0,3,10,9,11,7,6,1,8,5,4}, + {16,13,21,11,22,14,20,18,15,17,19,12,10,5,9,7,1,6,4,8,2,3,0}, + {18,14,21,12,19,11,22,13,20,15,17,16,7,9,4,2,0,5,3,6,10,8,1}, + {14,19,22,21,11,16,18,20,17,12,13,15,0,3,5,4,2,9,6,8,7,1,10}, + {11,13,17,19,22,21,12,18,15,14,16,20,3,6,8,1,4,2,7,9,0,10,5}, + {18,22,13,12,16,21,14,17,19,20,15,10,3,2,9,11,4,7,0,8,6,5,1}, + {18,15,12,16,13,17,19,20,22,14,11,21,2,4,6,1,3,5,0,8,7,10,9}, + {16,12,22,21,17,19,18,14,20,13,15,0,6,5,7,4,9,2,1,11,8,3,10}, + {20,22,13,17,14,19,16,15,21,18,12,1,3,7,4,10,0,2,9,5,6,8,11}, + {14,16,20,22,17,19,12,18,11,13,21,15,5,3,9,10,7,4,8,2,0,6,1}, + {19,13,21,14,16,12,18,11,15,20,17,22,2,4,7,6,5,8,1,3,0,9,10}, + {11,21,17,22,20,18,12,15,19,13,16,14,9,3,8,4,6,1,10,7,0,2,5}, + {16,18,17,12,21,19,15,22,13,14,20,6,8,11,4,9,5,3,1,10,7,0,2}, + {21,20,18,12,11,19,14,16,22,13,15,17,1,9,6,3,5,8,4,7,10,0,2}, + {20,15,19,13,22,14,12,21,18,16,11,17,1,0,9,6,4,3,10,5,8,7,2}, + {14,16,15,13,17,20,12,22,18,21,19,6,4,1,9,8,2,11,0,3,7,5,10}, + {17,20,15,19,22,21,18,13,16,12,14,1,3,11,10,7,9,6,4,2,8,5,0}, + {18,17,21,14,15,11,16,20,13,19,22,12,6,8,0,2,1,9,7,4,10,3,5}, + {21,15,13,16,11,19,12,18,22,20,14,17,3,10,6,0,4,2,5,9,8,7,1}, + } + }, + { 24, { + {21,16,14,22,13,23,17,20,19,18,15,12,11,8,0,2,5,3,10,6,1,4,7,9}, + {16,12,14,13,20,18,15,22,19,23,17,21,7,3,2,6,0,10,5,8,4,11,1,9}, + {14,16,13,22,21,18,12,17,15,23,20,19,4,10,0,3,1,5,7,11,2,6,8,9}, + {23,19,15,21,12,14,20,13,18,16,22,17,9,8,4,2,1,3,10,5,7,6,11,0}, + {19,21,20,15,18,17,12,16,13,23,22,14,8,5,11,3,9,0,6,2,7,10,4,1}, + {21,18,23,14,17,13,16,15,19,12,20,22,4,5,8,11,2,10,1,3,9,0,7,6}, + {19,14,22,20,17,13,15,18,12,21,16,23,7,10,8,6,9,4,1,11,3,5,0,2}, + {21,23,16,12,20,18,17,19,14,13,22,15,3,7,8,2,1,6,0,9,4,11,10,5}, + {15,14,23,18,20,12,17,19,22,13,16,21,0,4,8,3,10,11,9,7,5,1,6,2}, + {19,23,20,18,13,15,21,17,12,16,14,22,3,7,2,5,10,6,1,0,8,4,9,11}, + {14,18,23,22,20,19,12,13,16,15,17,21,10,2,0,4,6,8,1,11,3,7,5,9}, + {16,13,22,12,17,23,21,20,18,14,19,15,3,6,8,0,5,10,2,7,11,9,4,1}, + {21,17,15,20,14,18,13,23,19,22,16,12,11,7,0,10,1,3,6,9,5,8,4,2}, + {13,21,12,14,18,23,22,17,20,19,16,15,11,4,8,10,9,6,1,3,5,2,7,0}, + {23,22,16,20,13,19,17,14,12,15,18,21,8,10,9,4,0,5,7,6,2,11,1,3}, + {21,23,22,18,16,14,13,17,12,20,19,15,8,6,0,5,4,10,3,7,9,11,2,1}, + {13,23,17,21,19,22,18,15,14,20,16,12,3,2,4,8,6,9,1,0,5,7,11,10}, + {14,16,18,20,23,19,21,15,12,17,22,13,5,3,11,1,9,8,7,10,4,6,0,2}, + {17,12,21,16,20,18,23,13,19,22,14,15,6,2,5,10,1,7,9,0,3,8,11,4}, + {16,12,17,22,21,19,23,20,14,13,15,18,7,4,6,5,8,2,0,10,9,11,3,1}, + {12,15,21,18,16,13,20,14,17,22,23,19,3,5,1,7,4,9,2,11,6,10,8,0}, + {16,18,23,19,14,13,15,22,12,20,17,21,8,10,4,6,0,1,9,3,5,11,7,2}, + {17,21,16,18,20,14,19,15,22,13,23,12,11,5,7,6,1,8,4,9,3,0,10,2}, + {18,15,13,12,20,14,23,17,16,22,21,19,1,9,6,4,2,8,11,5,7,0,10,3}, + {20,14,19,16,21,23,13,22,18,17,12,15,1,8,7,3,11,0,6,10,5,9,4,2}, + {13,22,20,17,21,19,16,23,12,14,18,15,10,0,5,11,2,1,7,6,9,4,3,8}, + {23,19,13,16,20,12,15,21,18,17,22,14,0,6,3,10,8,11,4,1,5,7,2,9}, + {12,20,17,13,18,16,21,23,14,19,22,15,10,11,2,9,7,5,0,3,1,4,8,6}, + {19,15,22,16,23,12,14,13,17,21,20,18,3,7,6,8,11,9,5,0,10,1,4,2}, + {23,17,14,16,15,19,21,13,18,22,12,20,8,3,5,0,10,9,6,2,4,1,7,11}, + {21,18,22,16,13,15,17,12,20,19,14,23,9,0,5,1,11,3,7,6,8,4,10,2}, + {17,22,15,23,19,21,12,14,18,13,16,20,8,4,0,10,2,7,6,1,11,5,3,9}, + {15,21,17,14,19,23,18,20,13,22,12,16,6,8,4,9,11,2,5,7,3,1,0,10}, + {15,12,21,18,23,20,17,14,16,19,22,13,1,3,11,10,6,4,0,9,5,8,2,7}, + {17,20,22,16,14,18,15,19,21,13,12,23,6,3,0,2,9,11,4,7,5,10,8,1}, + {20,15,12,22,13,17,21,18,23,19,14,16,7,9,0,4,3,2,6,8,1,10,11,5}, + {19,13,12,17,23,21,15,18,16,14,20,22,10,1,9,6,8,0,7,3,2,5,11,4}, + {23,12,14,22,18,16,21,13,19,17,15,20,1,7,3,10,0,5,4,8,2,6,11,9}, + {13,18,16,21,23,15,20,19,22,14,17,12,9,8,7,6,3,2,11,5,0,10,4,1}, + {22,21,16,14,23,17,19,15,13,20,18,12,1,4,6,10,7,5,2,11,9,3,8,0}, + {12,17,22,14,18,15,23,20,16,19,21,13,11,9,6,5,7,0,10,3,8,1,4,2}, + {15,13,18,22,17,20,14,16,19,12,21,23,5,4,2,9,0,1,6,3,7,10,8,11}, + {17,23,21,15,13,22,16,14,19,18,12,20,4,1,10,6,11,8,7,5,3,0,2,9}, + {15,20,19,23,18,17,21,16,12,14,13,22,7,11,5,1,4,3,10,0,2,8,6,9}, + {19,13,20,14,16,23,21,22,18,15,12,17,2,5,3,9,0,7,6,8,11,4,1,10}, + {20,19,23,15,22,18,21,16,13,17,12,14,10,2,9,11,8,4,3,1,6,5,0,7}, + {22,13,15,20,16,21,23,18,17,12,19,14,10,1,6,8,3,7,4,9,5,11,0,2}, + {21,18,20,15,19,16,22,13,17,12,23,14,11,6,3,8,0,7,9,4,1,10,5,2}, + {14,22,18,16,17,12,15,21,20,19,13,23,8,4,7,6,3,1,10,9,5,0,11,2}, + {14,12,17,20,22,16,15,23,13,21,19,18,8,11,7,6,4,2,9,3,1,10,5,0}, + {16,18,20,15,13,22,17,23,19,14,21,12,2,1,5,9,7,6,3,0,10,8,11,4}, + {13,16,19,23,18,15,21,12,20,14,22,17,0,3,1,7,9,8,4,2,6,10,5,11}, + {21,23,17,22,12,19,15,18,16,20,14,13,4,5,2,10,0,7,1,11,9,8,3,6}, + {14,19,22,13,15,21,17,20,23,12,16,18,7,2,4,11,6,3,10,1,5,0,9,8}, + {19,17,15,23,20,12,18,21,14,22,16,13,10,4,2,5,11,3,0,8,1,7,9,6}, + {13,18,19,17,22,20,16,21,15,14,12,23,7,4,2,8,6,1,11,10,5,9,0,3}, + {22,14,19,12,17,21,20,16,23,15,13,18,10,4,11,3,7,5,1,6,2,8,0,9}, + {23,12,14,17,22,20,13,15,19,16,18,21,2,0,7,11,1,9,6,5,4,8,3,10}, + {22,12,13,18,20,23,15,19,17,14,21,16,7,10,4,3,5,8,0,2,11,1,9,6}, + {19,15,17,21,12,16,22,20,14,13,18,23,1,10,9,8,4,0,3,6,2,11,7,5}, + {19,17,15,20,14,18,16,22,21,23,12,13,11,2,4,10,7,0,5,3,9,8,1,6}, + {14,23,17,13,16,19,18,21,12,20,22,15,8,11,5,10,6,4,7,9,0,3,2,1}, + {22,15,17,20,23,14,21,16,12,18,13,19,11,8,5,9,7,0,6,2,4,3,10,1}, + {23,18,13,20,15,17,14,21,16,19,22,12,11,5,0,2,1,7,4,8,3,10,6,9}, + {12,19,22,14,17,13,16,15,23,21,18,20,7,1,10,6,9,11,3,0,2,5,4,8}, + {17,23,14,12,20,15,21,13,19,18,22,16,4,10,7,0,6,3,2,5,11,9,8,1}, + {19,23,22,15,14,16,18,17,12,20,13,21,7,2,4,9,3,10,6,8,1,5,0,11}, + {20,23,12,18,14,16,19,22,13,15,17,21,11,5,2,4,0,8,1,3,6,10,7,9}, + {22,17,12,13,18,15,19,23,14,20,16,21,2,10,9,4,8,5,11,6,1,3,0,7}, + {21,18,17,19,23,13,20,22,14,16,15,12,9,8,10,2,0,6,5,3,1,7,4,11}, + {22,20,18,17,14,21,23,15,13,16,19,12,3,5,0,9,1,8,10,2,7,4,6,11}, + {20,14,21,17,13,19,16,18,23,12,15,22,1,0,3,7,4,8,10,11,6,9,2,5}, + {14,17,16,18,13,15,20,12,22,19,21,23,9,0,2,5,3,10,11,7,4,6,1,8}, + {21,12,20,22,19,13,15,14,17,16,18,23,4,6,10,9,8,3,5,11,1,0,2,7}, + {16,14,23,21,13,19,18,17,15,12,20,22,0,4,1,6,3,7,5,10,8,2,9,11}, + {18,17,22,14,16,19,13,21,20,15,23,12,5,4,9,6,0,7,11,10,2,3,1,8}, + {14,16,13,19,22,15,23,18,21,17,12,20,11,0,9,8,2,5,10,7,3,1,4,6}, + {22,16,18,19,12,21,15,17,23,13,20,14,10,2,11,8,1,0,3,6,5,9,7,4}, + {21,15,12,20,19,13,16,23,18,22,14,17,5,3,10,0,6,2,7,1,8,4,9,11}, + {16,19,12,17,13,20,22,15,14,21,18,23,11,0,2,6,8,1,10,3,5,4,7,9}, + {16,15,21,20,19,14,13,22,18,12,23,17,6,2,5,10,0,11,9,4,3,8,7,1}, + {12,15,22,13,20,18,14,23,21,19,17,16,0,3,6,1,9,10,5,11,4,8,2,7}, + {16,21,20,18,23,22,19,17,14,13,15,12,11,9,1,5,0,2,3,6,4,8,10,7}, + {12,21,14,20,17,23,19,15,18,22,13,16,8,4,2,6,9,5,1,7,10,3,0,11}, + {22,13,16,14,18,23,20,19,17,15,21,12,5,7,3,6,0,8,11,1,9,4,10,2}, + {18,20,19,21,23,15,12,13,17,14,22,16,7,5,2,11,9,8,6,3,1,4,0,10}, + {12,14,23,17,13,15,18,20,16,19,22,21,6,8,0,7,10,3,5,11,2,1,4,9}, + {23,17,13,15,21,12,14,19,18,22,16,20,5,9,3,6,10,2,1,8,7,11,0,4}, + {15,12,19,17,20,14,18,16,13,22,21,23,11,9,6,10,2,8,0,3,7,1,5,4}, + {18,13,15,20,19,12,14,16,22,17,23,21,11,0,6,10,7,5,4,1,3,9,8,2}, + {19,22,15,20,14,12,16,23,21,17,13,18,1,10,3,6,5,2,11,9,4,8,7,0}, + {17,15,21,20,14,19,12,23,16,22,13,18,0,8,6,3,5,1,4,2,11,10,7,9}, + {19,23,22,16,20,12,18,15,17,14,13,21,5,10,11,7,3,0,2,8,4,6,9,1}, + {16,19,23,14,21,20,22,15,17,12,13,18,0,4,3,10,2,5,8,7,9,1,6,11}, + {13,18,16,12,22,17,23,20,19,14,21,15,3,0,5,4,10,11,6,8,7,2,9,1}, + {14,19,16,21,17,15,13,23,22,20,18,12,1,5,8,10,0,4,6,7,11,3,9,2}, + {15,18,22,20,16,14,12,17,23,21,13,19,1,11,10,8,5,7,4,6,3,9,2,0}, + {13,12,23,15,20,22,19,17,18,16,21,14,8,0,2,9,3,7,5,11,4,10,1,6}, + {14,21,19,15,13,17,12,20,22,16,23,18,10,0,5,3,9,1,8,6,2,11,4,7}, + {18,16,23,15,17,13,21,19,22,20,12,14,5,0,4,6,8,10,1,2,7,3,11,9}, + } + }, + { 25, { + {18,16,21,23,15,22,19,24,17,14,20,13,10,2,8,6,3,5,7,9,4,11,12,1,0}, + {24,12,17,15,21,23,20,22,19,14,18,13,16,10,1,3,9,2,8,5,0,7,4,11,6}, + {20,18,13,22,21,14,24,12,15,23,17,19,16,8,4,1,3,5,11,2,6,10,7,9,0}, + {23,17,21,20,16,15,13,24,14,19,22,18,4,3,5,11,12,1,6,9,8,2,10,0,7}, + {17,13,16,22,21,24,15,14,23,20,18,12,19,8,7,6,10,0,5,4,1,11,3,9,2}, + {22,24,13,16,15,20,14,19,18,23,17,21,11,1,6,0,8,2,5,9,3,7,4,10,12}, + {19,15,23,13,17,21,18,22,20,24,14,16,10,3,8,11,6,1,9,0,2,7,12,5,4}, + {18,24,12,14,23,22,21,20,19,17,16,13,15,5,7,11,3,10,2,8,0,4,1,9,6}, + {15,22,21,24,18,12,17,19,16,23,14,20,13,6,10,4,0,11,5,7,3,2,1,9,8}, + {15,13,19,22,20,23,14,24,17,16,18,21,7,12,8,0,11,6,4,2,10,1,9,5,3}, + {14,18,24,13,19,17,22,23,20,15,12,21,16,7,5,11,0,4,2,10,8,6,9,3,1}, + {15,14,12,16,21,18,17,24,13,23,20,19,22,8,1,5,10,6,3,11,0,2,7,9,4}, + {22,16,24,17,21,15,20,13,12,23,18,14,19,11,5,7,10,3,1,6,4,9,0,8,2}, + {21,18,13,19,24,16,14,23,22,15,20,17,5,12,6,1,4,11,7,0,9,3,8,10,2}, + {17,19,16,22,14,13,23,21,18,15,20,12,24,8,3,0,2,5,7,9,10,6,11,4,1}, + {20,14,22,12,18,16,15,19,17,21,24,23,13,6,0,9,2,5,4,3,1,11,8,7,10}, + {15,22,24,16,13,20,19,18,12,21,17,23,14,10,4,3,8,5,9,1,6,11,0,7,2}, + {16,12,20,24,18,14,17,15,22,13,23,19,21,11,1,5,2,4,7,8,0,10,6,3,9}, + {21,13,20,14,19,23,15,18,16,22,24,17,6,8,10,12,2,11,1,5,7,0,4,3,9}, + {18,21,15,23,14,20,16,24,19,17,13,22,8,4,5,0,2,9,7,1,11,6,3,12,10}, + {24,20,19,21,12,17,14,16,15,22,18,23,13,10,6,5,8,11,3,2,1,9,0,4,7}, + {18,15,24,12,17,16,14,20,23,22,19,21,13,0,8,4,6,9,1,11,2,5,3,7,10}, + {18,23,15,22,12,24,21,16,13,14,20,17,19,8,5,2,0,4,3,7,9,6,10,1,11}, + {15,16,13,24,23,19,14,17,12,20,18,22,21,9,11,6,10,7,3,5,2,8,0,4,1}, + {16,22,20,19,15,18,12,14,21,23,17,13,24,8,7,0,11,6,10,3,5,4,1,9,2}, + {22,14,17,16,20,19,23,15,18,24,13,21,10,4,11,6,1,7,2,0,5,9,3,12,8}, + {14,24,16,22,21,19,18,15,23,13,17,20,1,9,4,7,2,6,8,12,11,10,5,0,3}, + {24,14,16,19,22,15,17,20,18,13,23,21,11,1,8,3,6,5,9,0,12,4,10,7,2}, + {19,13,15,22,20,18,12,17,24,21,23,16,14,1,7,2,11,6,5,0,4,9,3,10,8}, + {23,13,22,15,21,14,17,24,18,19,16,20,8,11,5,2,10,1,4,9,0,12,7,6,3}, + {20,15,19,23,14,24,21,18,22,17,13,16,10,2,7,5,0,8,6,3,1,9,4,12,11}, + {18,24,19,15,13,17,14,20,22,16,21,23,7,12,11,8,0,10,1,5,4,2,3,6,9}, + {12,16,23,19,14,18,13,21,15,20,24,17,22,6,11,8,7,3,5,4,9,1,0,2,10}, + {17,13,14,23,15,12,16,18,24,20,22,19,21,9,4,11,1,8,0,10,3,7,2,6,5}, + {19,16,22,15,21,23,17,20,14,13,18,24,0,4,12,3,1,9,6,8,11,10,2,5,7}, + {24,23,20,15,21,18,16,19,14,22,17,13,11,7,10,0,9,2,8,4,3,5,1,12,6}, + {21,18,17,19,13,15,14,20,23,22,24,16,11,0,7,9,3,6,4,10,5,12,2,1,8}, + {17,22,12,21,15,20,19,16,23,18,24,13,14,4,6,1,11,9,8,2,0,3,5,7,10}, + {15,13,19,16,21,14,24,23,18,22,17,12,20,1,9,0,3,11,8,5,10,4,2,7,6}, + {21,19,18,13,24,16,14,17,15,23,20,22,8,7,9,12,5,2,0,10,4,3,11,1,6}, + {17,20,14,24,19,23,18,16,13,15,22,21,0,7,11,10,12,4,8,1,3,6,5,9,2}, + {16,18,17,23,13,21,15,19,14,20,22,24,1,9,10,4,0,5,3,7,6,11,2,12,8}, + {22,21,18,13,23,15,17,14,19,24,20,16,0,11,5,8,7,1,9,4,2,10,6,12,3}, + {14,16,15,22,24,17,20,21,13,19,23,18,3,8,10,9,6,5,11,2,4,7,12,1,0}, + {16,14,23,12,18,21,20,17,24,13,22,19,15,5,7,2,9,11,6,4,0,10,8,3,1}, + {22,20,18,12,17,19,13,15,21,23,16,14,24,9,11,6,4,10,3,1,7,8,0,5,2}, + {17,13,15,20,22,24,19,16,21,23,14,18,11,6,1,10,7,2,3,5,8,0,4,12,9}, + {16,12,15,19,13,18,24,21,23,17,14,20,22,8,2,5,3,11,9,7,0,6,4,1,10}, + {20,19,21,23,14,16,18,13,15,22,24,17,6,10,4,8,5,1,12,7,0,2,9,3,11}, + {13,21,24,19,22,18,23,17,14,20,15,16,0,10,5,12,1,11,8,3,7,9,4,6,2}, + {16,13,12,20,23,17,15,24,19,14,22,18,21,10,4,6,3,5,11,2,0,8,7,9,1}, + {17,12,19,24,14,21,20,18,13,16,23,22,15,0,10,11,4,6,2,1,9,5,8,3,7}, + {24,19,23,17,13,15,14,18,16,20,22,21,10,1,8,2,7,5,4,6,0,3,11,9,12}, + {17,14,18,16,22,21,24,13,20,15,23,19,11,6,8,5,7,1,12,2,9,3,0,4,10}, + {13,22,24,18,17,16,20,23,15,21,19,12,14,8,3,5,11,10,0,6,9,1,4,2,7}, + {14,16,23,21,15,20,24,17,19,13,22,12,18,7,4,6,1,8,5,2,11,9,10,3,0}, + {18,23,17,15,14,22,19,24,21,16,13,20,6,2,4,7,11,9,8,10,0,5,1,12,3}, + {17,15,24,14,20,16,19,18,23,21,13,22,10,4,1,6,7,5,3,11,2,9,8,0,12}, + {16,18,14,21,22,20,19,15,13,23,17,24,11,10,8,2,7,9,4,0,5,3,6,1,12}, + {16,23,13,18,12,22,15,19,21,14,20,17,24,11,6,10,4,8,7,0,2,1,9,5,3}, + {14,16,18,12,22,21,20,23,15,17,19,13,24,11,0,8,5,9,2,10,6,1,4,7,3}, + {19,13,17,24,22,12,20,18,14,16,15,23,21,4,0,3,11,10,1,9,8,2,5,7,6}, + {18,21,12,14,22,20,24,16,19,15,23,17,13,6,11,0,10,8,2,7,3,5,9,1,4}, + {24,14,19,13,23,22,15,21,20,18,17,16,6,0,10,12,7,1,4,2,8,3,5,9,11}, + {20,18,13,23,21,16,15,17,19,14,24,22,6,12,3,11,5,0,10,7,9,8,4,2,1}, + {19,20,23,16,14,24,13,15,18,17,22,21,1,11,2,7,5,9,8,0,10,6,4,12,3}, + {17,13,22,16,24,20,14,23,21,18,15,19,0,2,9,1,6,11,8,4,3,12,7,10,5}, + {21,14,22,15,12,19,23,17,16,18,20,24,13,8,10,5,4,3,9,2,6,0,11,7,1}, + {14,19,15,21,23,22,18,24,17,16,20,13,4,1,3,6,9,7,11,5,8,2,12,10,0}, + {17,16,18,24,20,13,22,14,21,19,15,23,8,10,4,0,3,5,7,11,1,6,2,12,9}, + {19,21,17,14,24,22,20,15,23,18,16,13,0,8,3,7,4,5,12,9,6,1,11,2,10}, + {19,14,21,15,12,22,18,16,23,20,17,24,13,0,4,10,11,6,8,3,1,5,2,7,9}, + {15,13,14,19,21,17,16,23,18,20,24,22,6,12,7,10,8,3,11,5,9,1,4,0,2}, + {22,14,21,15,19,24,16,20,18,13,17,23,11,6,2,8,10,5,3,9,12,7,1,0,4}, + {17,19,22,13,20,16,23,14,21,18,15,24,12,8,6,10,2,0,7,3,11,1,9,5,4}, + {14,23,22,20,18,15,19,17,13,16,24,21,1,12,2,5,7,11,10,6,3,8,0,9,4}, + {15,12,14,20,17,22,19,21,23,16,13,24,18,8,0,5,3,1,6,9,11,4,7,10,2}, + {16,15,19,21,12,14,13,24,22,20,18,23,17,0,8,11,9,4,10,2,6,3,1,5,7}, + {17,19,14,18,21,12,15,22,16,13,20,24,23,5,7,11,10,1,9,8,3,0,2,4,6}, + {13,16,24,23,17,19,14,12,21,15,18,22,20,10,6,1,5,7,0,3,11,2,4,9,8}, + {16,23,12,15,18,21,17,22,19,24,20,14,13,1,7,10,2,8,0,6,9,5,11,4,3}, + {17,20,13,12,15,23,22,19,24,16,18,21,14,1,4,3,9,6,8,7,5,10,0,2,11}, + {22,19,24,15,20,17,21,16,14,23,18,13,11,6,10,5,1,8,12,7,3,0,2,4,9}, + {22,16,18,21,23,15,20,17,14,13,19,24,3,1,7,6,2,12,5,10,9,11,0,4,8}, + {18,15,21,24,13,20,19,23,16,14,22,17,0,4,1,5,3,6,10,7,11,2,8,9,12}, + {13,23,18,24,15,22,14,20,17,16,19,21,3,12,7,5,4,2,11,6,8,0,10,9,1}, + {21,15,20,18,23,22,14,24,17,19,13,16,3,8,12,4,11,6,5,9,2,1,10,0,7}, + {12,17,19,21,15,18,16,22,20,14,13,23,24,10,9,11,7,0,2,8,6,4,1,5,3}, + {14,24,19,12,20,22,18,23,16,21,17,15,13,2,7,11,0,5,9,8,1,6,3,10,4}, + {16,23,21,19,18,20,12,15,14,22,24,17,13,11,7,10,3,9,6,8,0,4,5,2,1}, + {24,17,22,20,16,14,19,21,23,12,18,15,13,1,0,2,4,9,8,3,7,5,11,6,10}, + {17,16,19,21,20,15,13,22,24,18,14,23,6,8,2,5,12,0,9,10,4,3,7,11,1}, + {18,22,16,23,14,19,15,13,21,20,17,24,5,7,4,8,10,12,0,11,3,9,1,6,2}, + {23,13,20,15,24,19,17,22,18,21,14,16,11,6,0,5,7,2,8,4,1,10,9,3,12}, + {16,22,19,14,24,21,17,23,20,18,15,13,3,0,7,1,5,6,4,10,9,11,2,12,8}, + {19,16,21,20,24,17,13,15,22,12,14,18,23,1,10,4,0,7,3,2,8,5,6,11,9}, + {22,20,23,15,14,24,18,17,19,16,21,13,10,6,0,5,9,12,4,8,7,2,11,1,3}, + {14,23,17,15,21,16,20,19,18,13,24,22,1,9,5,3,12,11,6,2,8,4,7,0,10}, + {16,19,17,21,15,22,18,23,14,13,20,24,11,12,5,0,7,3,8,1,6,2,9,4,10}, + {19,16,20,22,17,18,21,14,23,13,24,15,4,6,5,3,0,2,9,11,8,1,10,12,7}, + } + }, + { 26, { + {14,20,22,25,13,23,16,24,15,18,21,19,17,10,11,5,3,1,9,8,4,12,6,2,0,7}, + {19,22,17,16,23,25,24,13,21,20,15,14,18,10,3,2,9,11,4,1,7,0,8,6,12,5}, + {25,24,23,15,22,14,18,13,16,21,17,20,19,3,11,8,2,1,4,6,5,7,0,12,10,9}, + {24,19,17,23,20,16,21,14,18,15,25,22,13,8,7,12,0,9,4,11,5,1,10,3,2,6}, + {17,22,13,21,23,25,15,24,14,18,20,19,16,2,11,6,0,10,8,1,12,9,4,7,3,5}, + {14,24,23,16,20,13,18,21,15,17,19,22,25,7,11,6,10,9,2,5,4,12,0,8,1,3}, + {13,22,25,16,19,23,15,24,21,14,18,17,20,12,8,4,2,5,0,7,9,11,1,10,3,6}, + {19,25,18,14,17,22,20,15,24,16,21,23,13,12,11,10,0,7,1,9,5,8,4,6,3,2}, + {22,21,16,13,18,17,14,19,23,25,24,15,20,12,8,2,0,3,6,5,10,9,11,7,1,4}, + {20,17,16,21,14,24,19,15,25,23,18,22,13,1,7,3,11,5,4,9,0,12,10,6,2,8}, + {13,21,17,16,25,23,15,18,14,20,19,24,22,12,0,8,7,3,10,4,9,5,1,6,2,11}, + {24,15,18,14,21,23,25,17,16,22,20,19,13,10,1,0,2,4,7,5,9,8,3,12,11,6}, + {13,20,16,21,19,15,17,14,24,18,25,23,22,10,1,5,2,4,9,12,0,11,6,3,7,8}, + {19,23,13,17,14,22,16,25,20,15,21,24,18,8,10,9,11,3,0,12,2,7,5,1,6,4}, + {15,13,20,22,17,19,23,21,16,18,24,25,14,11,8,5,4,3,0,2,7,1,9,6,10,12}, + {15,14,23,25,18,16,20,24,19,17,22,21,13,1,0,5,7,9,4,8,6,11,3,2,10,12}, + {14,22,24,18,16,15,13,21,25,20,17,19,23,2,6,9,1,11,4,10,8,12,0,5,7,3}, + {20,14,23,16,15,13,17,22,24,18,25,21,19,5,12,4,3,7,2,8,0,11,6,9,1,10}, + {16,21,15,25,19,17,24,13,18,14,23,20,22,11,2,1,0,7,8,5,10,9,12,4,6,3}, + {19,15,17,16,14,13,20,23,18,25,21,24,22,6,12,4,3,2,8,0,5,9,10,7,11,1}, + {16,24,23,19,17,21,18,20,15,25,22,14,13,9,0,2,4,12,3,5,1,7,11,8,6,10}, + {14,17,22,15,21,18,24,16,13,23,19,25,20,4,11,8,12,2,7,6,5,1,0,9,3,10}, + {22,16,21,13,23,17,24,14,19,15,20,25,18,11,0,8,1,5,12,2,10,9,6,4,3,7}, + {21,23,15,24,20,25,22,18,17,13,14,19,16,3,12,7,10,0,4,6,1,9,5,2,11,8}, + {22,21,24,13,16,15,19,18,25,17,23,14,20,3,11,0,9,8,1,6,10,7,5,2,12,4}, + {18,13,24,20,19,21,16,23,15,22,25,14,17,5,3,7,10,1,11,9,4,6,12,2,0,8}, + {17,24,23,20,18,13,16,14,25,15,19,22,21,8,7,2,6,0,11,10,3,12,4,9,1,5}, + {20,19,23,16,22,25,17,21,14,18,15,24,13,10,3,1,11,0,8,6,7,12,4,2,9,5}, + {13,17,14,19,25,20,23,15,24,21,18,16,22,4,11,12,0,2,5,7,9,6,10,1,3,8}, + {17,20,19,24,16,18,21,13,15,22,25,14,23,7,1,5,0,2,12,10,11,4,9,8,3,6}, + {13,23,15,24,19,22,18,17,25,14,21,16,20,12,6,1,11,0,4,9,7,2,5,8,3,10}, + {16,22,18,21,20,24,15,23,17,14,19,25,13,6,8,10,7,11,2,1,4,3,12,0,5,9}, + {16,21,24,20,13,19,22,18,15,25,14,23,17,11,8,0,7,6,5,3,1,9,12,10,2,4}, + {24,15,23,14,22,19,16,13,17,25,21,20,18,1,8,0,7,4,3,9,2,12,6,11,5,10}, + {17,24,15,14,22,18,20,19,23,21,25,13,16,9,7,1,4,12,11,2,8,6,0,3,10,5}, + {18,24,23,17,13,25,16,20,22,19,14,21,15,4,1,7,0,11,3,9,12,5,2,6,8,10}, + {15,14,25,23,21,13,17,19,16,18,22,24,20,2,4,6,1,11,0,3,10,7,9,8,5,12}, + {18,23,16,22,15,20,13,14,19,25,17,24,21,4,3,9,11,10,1,8,2,12,7,5,0,6}, + {24,15,13,14,20,22,19,23,18,21,25,17,16,6,3,5,11,4,10,12,1,0,2,7,9,8}, + {13,16,15,25,21,18,23,14,17,22,20,19,24,3,11,7,4,10,12,9,2,5,1,6,0,8}, + {14,21,17,22,19,15,24,23,18,20,16,25,13,7,2,5,10,12,11,9,1,4,0,8,6,3}, + {18,17,24,16,23,25,14,20,19,22,21,15,13,6,5,3,1,9,0,8,2,4,10,12,11,7}, + {20,25,15,21,16,18,24,22,19,23,14,17,13,7,11,2,0,12,10,6,5,3,8,4,1,9}, + {16,14,25,20,19,23,21,15,18,22,17,24,13,3,5,0,12,10,9,11,6,4,2,7,1,8}, + {22,25,16,14,18,23,17,15,19,24,21,13,20,0,7,12,6,2,4,10,1,8,11,3,9,5}, + {15,14,21,20,18,16,23,25,22,17,24,19,13,4,11,3,5,0,7,2,12,10,9,1,6,8}, + {20,25,19,23,17,22,15,14,18,13,21,16,24,3,8,6,1,0,9,5,4,10,2,7,12,11}, + {13,21,20,17,24,16,19,25,18,23,14,15,22,12,0,9,7,4,1,10,3,5,8,11,6,2}, + {13,17,23,16,14,20,19,22,21,15,18,25,24,11,7,12,3,1,6,8,4,0,5,10,9,2}, + {25,19,23,20,16,14,17,24,21,18,13,15,22,12,5,11,4,2,1,9,7,6,0,8,3,10}, + {23,16,14,20,19,15,22,21,18,17,25,24,13,11,8,4,9,7,2,10,3,6,1,5,0,12}, + {21,24,15,25,17,14,18,23,19,22,16,20,13,5,11,3,0,10,6,8,7,12,2,9,1,4}, + {23,22,24,19,21,13,25,20,15,17,14,18,16,2,6,9,3,8,0,11,5,1,12,10,4,7}, + {14,18,16,20,22,24,21,25,13,17,15,23,19,7,3,11,4,0,6,8,10,9,5,2,1,12}, + {21,16,19,13,25,24,20,17,15,22,14,18,23,4,10,7,9,5,8,1,3,11,12,2,6,0}, + {22,19,16,15,20,23,13,25,18,17,14,24,21,2,11,0,7,1,5,8,9,12,4,3,10,6}, + {21,16,23,14,22,15,19,13,24,17,20,25,18,9,8,3,7,12,2,4,11,0,6,5,1,10}, + {19,15,14,22,24,13,18,20,23,25,16,21,17,11,5,7,6,8,10,2,12,0,3,9,1,4}, + {16,25,22,24,18,15,17,23,19,13,21,20,14,12,10,3,7,1,8,0,9,6,11,2,5,4}, + {13,14,22,24,23,20,15,19,18,21,16,25,17,9,12,6,11,3,0,2,5,1,7,4,8,10}, + {14,13,15,25,17,19,16,22,18,20,23,21,24,8,5,2,4,3,7,6,9,11,1,10,12,0}, + {13,15,19,14,22,17,16,18,23,25,21,20,24,5,8,12,7,9,1,10,3,0,4,6,2,11}, + {13,19,24,18,15,17,21,14,25,22,20,23,16,3,11,8,12,5,10,1,6,9,7,0,2,4}, + {24,17,14,19,18,13,23,15,21,25,22,20,16,1,3,5,4,8,11,7,9,6,0,12,2,10}, + {24,15,21,17,13,16,20,25,19,23,18,22,14,12,11,10,9,2,8,3,1,7,4,6,5,0}, + {15,23,21,17,22,18,20,16,24,19,25,13,14,3,1,9,4,11,6,0,8,12,7,2,10,5}, + {25,22,16,20,24,13,21,18,15,23,19,14,17,1,8,4,9,3,10,5,0,7,11,6,2,12}, + {21,17,19,22,15,14,25,18,23,13,16,20,24,6,8,10,4,9,2,1,11,7,5,12,0,3}, + {21,13,18,17,24,19,22,15,14,23,16,20,25,12,8,11,3,9,5,7,6,4,1,0,10,2}, + {14,15,23,25,17,22,13,21,20,24,16,19,18,6,2,5,4,10,0,3,8,7,9,12,1,11}, + {16,18,13,23,20,19,15,17,21,25,24,14,22,12,2,8,6,4,1,10,7,0,11,3,5,9}, + {16,15,13,14,20,22,21,24,19,23,18,25,17,10,0,12,4,1,5,2,6,8,7,11,9,3}, + {17,24,19,15,14,13,16,22,21,25,23,20,18,12,5,10,1,7,6,11,9,4,8,2,0,3}, + {20,25,19,17,21,16,14,24,15,23,22,13,18,11,8,12,5,3,2,7,9,4,0,10,1,6}, + {13,22,15,21,18,25,20,14,16,17,24,23,19,7,9,2,12,6,5,11,8,3,1,0,10,4}, + {23,15,25,13,17,24,14,18,22,16,21,20,19,8,1,4,6,10,5,7,9,0,3,11,2,12}, + {20,13,21,19,23,15,18,17,25,14,22,24,16,12,10,8,4,2,1,3,0,7,9,6,5,11}, + {16,22,19,13,17,15,24,23,20,25,21,18,14,5,8,4,12,6,11,2,0,10,7,3,1,9}, + {20,18,13,22,24,17,16,21,19,15,23,14,25,7,5,0,9,4,2,8,6,12,3,10,1,11}, + {25,18,16,14,20,22,19,23,15,24,17,13,21,3,7,0,9,1,12,5,4,6,10,8,2,11}, + {24,17,14,16,18,23,19,15,20,25,22,21,13,12,8,2,5,0,7,9,1,10,6,4,3,11}, + {21,14,18,17,22,15,13,20,19,24,23,25,16,8,2,11,9,3,0,4,1,10,6,7,12,5}, + {18,24,22,21,16,19,14,15,23,13,20,25,17,12,0,5,4,8,2,11,7,6,1,10,3,9}, + {20,15,24,23,13,14,18,22,17,25,19,21,16,11,1,3,2,9,7,6,10,0,4,12,5,8}, + {20,24,13,19,25,22,18,23,16,15,14,17,21,7,11,4,8,0,5,3,9,12,1,2,6,10}, + {13,15,24,14,16,22,18,21,19,25,23,17,20,0,2,8,7,4,12,5,11,10,3,1,6,9}, + {17,20,15,22,19,14,16,24,23,25,21,18,13,2,0,10,12,11,1,9,5,6,4,8,7,3}, + {15,19,24,14,13,21,20,16,25,23,17,22,18,9,10,1,6,12,4,0,5,3,11,7,2,8}, + {20,22,14,19,15,21,25,16,18,24,17,23,13,10,2,6,9,12,11,1,3,8,4,0,5,7}, + {13,23,14,17,15,19,21,16,25,20,24,18,22,0,2,4,12,3,7,10,8,5,11,9,6,1}, + {14,22,23,25,21,17,15,24,20,19,13,16,18,5,10,9,3,8,12,6,0,7,4,2,1,11}, + {19,24,18,17,16,14,22,15,21,20,23,13,25,6,0,4,11,1,3,5,10,12,2,7,9,8}, + {18,16,24,20,13,22,17,21,15,23,25,19,14,11,9,0,7,6,4,3,5,10,12,8,2,1}, + {13,15,21,19,17,22,16,14,24,18,25,23,20,3,9,0,10,4,6,11,1,7,12,2,8,5}, + {19,13,16,22,15,14,21,23,20,17,24,18,25,0,5,4,10,9,11,8,12,3,6,7,2,1}, + {14,21,17,25,20,19,22,24,18,13,16,23,15,9,6,4,10,5,8,3,7,1,12,11,2,0}, + {25,13,23,21,17,22,24,16,15,20,19,18,14,11,9,7,4,1,0,10,6,12,3,5,2,8}, + {23,22,13,18,20,19,21,17,14,24,15,25,16,10,8,9,5,1,3,7,4,6,12,0,2,11}, + {19,17,22,15,13,20,25,23,18,24,14,21,16,4,10,3,2,9,6,8,5,1,11,7,12,0}, + {19,18,14,22,24,23,20,17,16,13,15,25,21,6,11,8,10,4,1,5,12,0,2,7,9,3}, + } + }, + { 27, { + {16,20,17,23,18,22,14,15,19,26,21,25,24,9,3,11,8,4,2,13,6,0,5,1,10,7,12}, + {26,19,23,21,15,17,14,24,25,18,22,16,20,7,13,10,9,2,11,6,1,5,4,3,0,12,8}, + {16,15,13,17,23,26,14,20,22,18,21,25,19,24,6,12,11,7,9,3,1,5,4,0,2,10,8}, + {19,18,22,14,26,23,16,21,15,25,17,24,20,5,12,9,11,8,1,3,13,6,4,10,2,0,7}, + {25,16,18,21,19,22,26,14,17,23,24,15,20,7,12,6,8,0,11,4,10,13,5,1,3,2,9}, + {23,17,25,19,16,15,21,18,22,24,26,14,20,10,1,5,9,8,12,7,11,0,3,13,6,4,2}, + {20,17,23,18,16,19,14,22,15,26,13,25,21,24,0,8,10,7,1,12,4,11,3,6,5,9,2}, + {25,15,20,16,22,21,23,18,24,17,14,26,19,8,10,1,7,9,2,12,11,5,4,0,13,3,6}, + {14,25,17,20,19,13,21,15,18,16,24,22,26,23,1,6,11,7,8,4,9,2,5,0,10,3,12}, + {20,13,24,23,25,16,19,15,18,22,17,21,26,14,1,10,8,6,11,9,7,3,0,5,2,12,4}, + {14,22,23,19,17,16,15,18,24,26,21,20,25,9,4,6,0,2,7,3,11,10,1,5,13,12,8}, + {25,22,19,16,14,13,23,20,26,17,21,18,24,15,12,3,1,7,10,8,4,0,9,5,11,2,6}, + {26,15,14,21,20,16,25,23,17,22,19,24,18,7,4,8,10,3,0,6,2,11,13,12,1,9,5}, + {21,20,17,14,23,13,26,19,25,16,22,15,18,24,12,8,4,0,9,6,1,3,7,11,10,5,2}, + {24,15,22,16,21,23,20,18,19,17,14,26,25,2,9,1,4,12,11,10,8,0,13,7,5,3,6}, + {20,25,24,21,18,26,16,15,17,23,22,19,14,12,11,2,6,8,4,5,0,3,10,1,9,7,13}, + {21,23,13,17,15,20,25,19,22,26,16,24,18,14,0,4,6,1,10,2,11,5,8,3,12,9,7}, + {16,23,25,20,24,21,17,15,14,26,18,19,22,8,10,7,12,2,4,0,6,11,5,13,1,9,3}, + {24,26,17,15,22,25,18,21,14,23,16,20,19,5,13,1,6,0,9,4,3,2,10,8,12,7,11}, + {15,14,21,23,22,18,25,24,26,20,17,16,19,7,1,9,2,4,6,11,0,12,10,3,13,5,8}, + {16,18,14,26,25,20,19,24,17,15,21,23,22,2,7,1,0,8,6,3,10,13,12,5,11,4,9}, + {16,25,23,19,18,17,22,20,14,21,24,26,15,4,12,9,6,2,8,0,7,1,10,13,5,3,11}, + {26,23,25,15,14,19,21,16,22,20,18,24,17,6,11,3,9,5,10,12,4,13,8,1,7,2,0}, + {14,21,23,22,25,19,18,15,17,24,26,16,20,3,11,6,1,10,8,7,2,4,13,12,5,0,9}, + {16,19,18,17,22,20,25,14,23,21,26,15,13,24,6,3,11,10,7,12,5,1,0,2,9,8,4}, + {21,18,20,15,19,14,26,22,16,23,17,24,13,25,0,4,2,5,8,11,1,3,10,7,9,6,12}, + {23,16,22,25,15,26,20,14,19,24,18,13,21,17,11,5,0,2,4,1,3,9,12,7,6,10,8}, + {13,17,25,14,16,21,19,22,15,23,20,24,26,18,5,3,10,8,12,7,2,9,0,6,4,1,11}, + {26,16,20,19,22,18,15,17,23,21,25,14,24,0,12,8,11,2,7,6,9,1,13,4,3,10,5}, + {16,18,14,24,20,23,17,13,25,21,19,26,22,15,1,8,2,10,5,3,7,0,4,11,12,6,9}, + {14,21,23,19,24,18,15,17,16,26,22,20,25,8,1,9,11,5,4,7,13,2,10,0,12,6,3}, + {22,24,14,19,17,23,21,20,26,16,15,18,25,0,10,7,5,3,2,8,12,6,9,11,1,13,4}, + {21,15,20,14,23,16,25,18,17,26,24,19,22,6,2,1,5,10,13,11,7,0,12,8,3,4,9}, + {17,23,14,16,22,19,25,21,20,18,26,15,24,0,9,6,1,13,11,10,3,12,4,7,2,8,5}, + {13,26,16,15,23,17,25,24,21,19,22,18,20,14,12,10,1,6,8,11,0,9,2,5,4,3,7}, + {17,22,20,19,26,15,13,24,14,25,21,18,16,23,7,4,11,6,2,9,5,8,3,10,1,0,12}, + {14,22,19,23,25,16,24,26,17,15,21,18,20,12,10,6,3,8,11,9,2,7,5,13,1,0,4}, + {22,26,25,18,17,14,23,20,15,21,16,24,19,5,0,3,10,12,8,6,2,9,13,1,4,11,7}, + {18,24,20,25,19,13,15,22,16,26,23,17,21,14,2,4,3,12,7,6,1,9,10,5,0,8,11}, + {26,24,22,18,23,13,16,19,17,14,20,15,25,21,8,11,2,4,3,6,5,10,7,9,1,12,0}, + {14,25,19,20,17,23,22,26,15,24,16,21,18,12,9,8,7,3,1,6,11,4,0,5,2,13,10}, + {17,24,22,19,13,26,21,20,23,14,16,25,18,15,11,8,2,12,7,3,1,5,4,9,0,10,6}, + {16,19,25,24,13,18,15,20,14,26,17,22,23,21,11,1,0,2,5,9,8,7,4,6,3,10,12}, + {14,24,18,25,21,23,17,19,22,26,16,15,20,11,9,13,10,6,5,8,1,3,12,7,2,0,4}, + {26,23,22,19,17,20,15,18,16,24,21,14,25,13,6,10,1,0,8,3,5,11,2,7,4,12,9}, + {25,16,23,18,21,20,17,22,19,24,15,26,14,0,4,1,11,6,5,12,8,2,7,13,3,10,9}, + {23,18,20,22,15,13,17,25,24,26,16,21,19,14,0,4,9,6,7,10,3,11,8,12,2,1,5}, + {25,22,24,21,14,17,19,16,20,18,23,26,15,4,7,3,1,5,2,6,12,0,8,10,9,13,11}, + {19,15,25,13,26,20,17,16,14,24,18,23,22,21,8,7,0,6,11,12,5,3,1,10,9,2,4}, + {21,16,22,15,24,20,17,26,25,18,14,23,19,0,3,2,13,8,6,12,4,7,11,10,5,9,1}, + {25,18,15,24,19,14,21,16,20,23,22,26,17,6,1,0,7,4,9,8,12,10,5,13,3,2,11}, + {23,20,17,19,16,13,22,26,18,21,25,15,24,14,6,5,3,1,7,4,2,10,8,11,9,12,0}, + {15,13,25,22,19,26,17,23,14,21,20,24,18,16,12,9,11,6,8,7,10,0,2,4,1,3,5}, + {26,14,24,19,18,15,20,23,21,25,22,17,13,16,4,10,12,5,8,2,6,11,7,0,9,3,1}, + {25,14,21,20,18,24,22,23,26,15,17,19,16,12,5,1,0,7,6,9,3,11,8,13,10,4,2}, + {18,24,21,25,15,17,23,20,22,19,16,26,14,11,2,3,10,4,1,12,5,7,6,13,9,0,8}, + {15,20,18,17,16,23,25,21,14,19,26,22,24,12,9,5,3,2,4,11,1,8,10,7,0,13,6}, + {21,15,23,14,22,24,19,18,25,20,16,26,17,9,13,5,7,10,11,4,8,3,2,1,12,0,6}, + {20,18,24,15,23,13,17,16,19,21,14,25,26,22,0,6,1,4,11,2,10,3,5,12,8,7,9}, + {26,17,15,24,22,25,16,21,19,23,18,20,14,11,6,3,9,5,4,12,2,1,13,7,10,8,0}, + {25,18,23,15,17,14,26,24,16,22,20,13,19,21,9,2,0,4,6,7,10,1,5,3,11,8,12}, + {18,14,17,16,26,23,25,19,20,22,21,24,15,0,13,5,12,6,10,3,1,8,4,7,2,9,11}, + {16,13,23,19,15,21,24,17,25,22,20,14,18,26,7,4,12,2,1,5,0,11,3,9,6,10,8}, + {25,15,18,20,22,17,19,14,26,21,23,24,16,5,7,3,1,13,12,6,11,9,0,10,2,4,8}, + {14,19,21,13,15,24,26,25,17,20,16,22,18,23,11,6,4,3,5,7,12,8,10,0,2,1,9}, + {22,26,16,15,19,24,17,21,14,20,23,18,25,5,0,4,9,1,11,7,12,6,8,3,13,2,10}, + {22,24,20,25,19,17,26,15,14,23,18,21,16,10,13,4,8,6,5,7,3,2,11,0,9,12,1}, + {18,16,20,24,14,21,17,13,22,26,23,19,15,25,6,12,1,10,8,9,4,0,2,7,3,11,5}, + {16,25,15,22,17,19,21,23,18,24,26,14,20,5,7,11,9,1,4,2,13,8,3,6,10,0,12}, + {21,17,22,15,14,26,18,25,24,16,23,19,20,5,3,6,1,11,8,0,2,13,4,10,9,7,12}, + {21,24,22,15,23,13,16,14,17,25,19,20,18,26,7,12,6,1,9,11,8,3,2,4,0,5,10}, + {18,22,25,16,13,19,17,23,14,21,20,26,24,15,12,0,6,10,7,2,4,9,3,5,1,8,11}, + {23,13,15,21,24,19,17,16,22,20,18,14,26,25,5,3,12,6,8,7,9,0,2,11,4,1,10}, + {19,15,22,25,14,21,18,26,17,20,16,23,24,0,4,1,5,8,6,13,7,10,2,11,9,3,12}, + {13,20,22,25,15,21,23,17,24,16,14,18,19,26,0,6,10,3,11,4,8,5,9,2,1,12,7}, + {16,26,15,24,22,21,14,17,19,18,25,20,23,10,2,13,6,4,1,5,7,9,12,8,11,3,0}, + {16,13,25,24,20,26,19,21,17,23,22,15,14,18,11,1,4,6,10,5,3,9,0,2,8,12,7}, + {19,21,15,24,13,22,20,26,25,17,16,23,18,14,7,0,5,3,10,12,9,4,1,8,6,11,2}, + {22,20,23,14,19,17,25,16,15,18,21,24,26,7,10,1,13,0,9,4,6,3,5,12,11,8,2}, + {26,20,19,18,14,23,21,17,16,22,24,15,25,12,2,13,4,9,1,5,8,11,7,3,0,10,6}, + {14,20,24,17,21,15,26,16,18,22,19,25,23,12,9,3,10,6,4,7,1,11,2,5,13,8,0}, + {15,24,18,23,16,25,22,20,17,19,26,14,21,12,9,2,0,11,4,8,5,1,7,6,13,3,10}, + {25,24,20,14,26,19,17,23,22,16,21,18,15,2,0,10,9,6,12,1,13,7,11,8,5,3,4}, + {21,26,25,15,24,20,18,19,23,22,17,14,13,16,10,3,6,11,8,7,5,0,9,12,4,2,1}, + {23,22,16,19,18,17,21,24,26,15,25,14,20,1,9,5,2,10,4,7,11,3,6,0,12,13,8}, + {19,18,20,13,22,17,16,15,24,14,26,21,23,25,10,6,12,1,11,7,3,8,5,4,2,0,9}, + {24,23,16,20,26,17,22,25,15,19,18,14,21,12,5,1,4,3,7,11,2,8,10,9,13,6,0}, + {18,22,19,21,13,17,20,23,25,24,26,16,15,14,11,8,0,4,6,9,7,2,12,3,5,10,1}, + {21,14,17,23,15,24,16,18,20,13,26,22,25,19,1,3,2,9,7,6,8,0,4,12,5,11,10}, + {22,24,23,14,25,17,15,20,18,21,19,26,16,6,1,11,9,13,10,2,7,4,0,8,12,3,5}, + {14,26,23,19,24,22,17,21,16,25,18,15,20,4,9,10,1,7,3,8,6,0,2,13,12,11,5}, + {20,15,21,16,26,17,13,24,23,19,22,18,14,25,7,3,6,9,1,4,8,10,0,12,11,2,5}, + {15,19,26,17,22,16,21,18,25,20,23,14,24,2,0,10,3,8,13,11,5,7,9,1,4,12,6}, + {18,26,14,19,17,20,15,22,24,16,25,21,23,2,9,5,0,8,3,7,12,13,11,6,4,10,1}, + {26,22,17,14,16,18,15,20,25,19,24,21,23,11,4,12,1,10,3,9,8,2,13,7,6,5,0}, + {23,16,25,22,24,21,14,19,15,17,13,26,18,20,6,4,1,10,12,7,11,0,3,9,8,2,5}, + {25,21,26,24,19,23,17,22,15,18,20,16,14,6,10,5,7,3,2,4,8,0,9,12,1,11,13}, + {24,21,16,18,20,17,19,26,22,25,23,13,15,14,1,12,2,5,3,6,4,11,8,10,0,9,7}, + {13,26,22,18,20,23,14,19,25,21,16,15,24,17,6,9,5,0,4,7,10,12,1,8,11,3,2}, + {22,26,18,25,17,16,24,21,14,20,15,23,19,12,5,4,7,1,13,2,10,3,6,8,0,11,9}, + } + }, + { 28, { + {23,14,25,27,16,26,19,24,21,15,22,20,17,18,11,6,3,5,4,9,7,12,10,1,0,2,8,13}, + {27,14,21,23,22,26,18,25,20,15,17,19,16,24,3,5,0,10,9,7,13,4,2,12,1,6,11,8}, + {15,23,19,14,24,20,18,26,17,21,25,22,16,27,6,12,3,9,13,11,7,2,8,5,0,4,1,10}, + {16,18,27,21,24,26,25,15,20,22,17,19,23,14,7,0,3,1,6,9,13,11,8,5,4,10,2,12}, + {23,20,15,19,18,27,26,22,25,21,17,14,24,16,12,11,9,7,4,3,1,0,8,13,5,10,6,2}, + {15,20,17,26,25,19,18,23,27,21,16,24,22,14,8,10,2,9,11,0,13,7,6,5,12,1,3,4}, + {24,27,16,23,26,17,19,21,20,25,22,15,14,18,13,6,11,8,12,3,7,1,10,4,9,5,0,2}, + {22,26,23,21,24,27,14,18,15,20,19,17,25,16,9,3,7,12,8,11,0,5,10,4,2,6,1,13}, + {26,22,19,24,21,20,25,17,15,23,14,16,18,27,10,8,4,12,3,6,0,13,5,7,11,2,9,1}, + {15,19,21,26,17,24,18,14,25,22,16,20,27,23,1,7,9,2,4,0,8,5,3,13,11,10,12,6}, + {15,17,14,22,27,23,18,21,20,19,24,16,26,25,12,11,1,9,0,10,8,3,7,2,6,13,4,5}, + {26,18,21,19,22,16,17,15,27,23,20,24,14,25,13,3,6,9,7,5,12,1,11,4,10,0,8,2}, + {18,17,23,21,27,19,24,20,15,25,14,26,16,22,13,3,10,8,11,2,7,5,0,9,6,4,1,12}, + {23,25,27,16,19,15,20,21,18,26,22,24,17,14,6,11,10,8,7,13,1,4,3,2,0,9,5,12}, + {22,26,16,14,20,23,25,18,24,17,21,27,19,15,1,5,9,7,6,13,4,3,2,11,8,0,12,10}, + {23,17,25,18,21,15,26,19,22,16,14,24,27,20,9,5,7,4,3,11,1,12,6,13,8,0,2,10}, + {20,18,17,24,19,21,26,25,27,16,23,15,22,14,6,2,5,8,0,4,10,3,12,9,1,7,13,11}, + {19,25,21,18,22,16,15,14,24,26,23,20,27,17,0,2,12,1,3,10,8,11,5,7,6,13,9,4}, + {21,27,18,26,20,17,16,25,24,23,22,14,19,15,6,9,8,12,7,0,11,10,13,4,3,2,1,5}, + {22,26,21,18,27,23,19,24,16,20,15,17,25,14,13,0,2,5,3,6,4,8,12,11,10,7,1,9}, + {24,17,27,16,25,22,19,15,26,18,23,20,14,21,7,12,8,4,3,1,6,10,9,2,11,0,5,13}, + {22,24,17,15,19,16,14,25,21,20,27,23,18,26,11,8,4,12,1,7,3,0,6,2,9,5,13,10}, + {18,15,19,24,21,16,25,23,20,26,14,22,17,27,3,1,0,5,12,4,8,11,6,10,13,2,9,7}, + {20,16,23,14,24,21,27,19,18,25,22,15,26,17,12,7,9,0,2,11,3,10,13,5,8,1,4,6}, + {25,24,21,14,22,15,19,27,18,26,23,17,20,16,6,11,10,0,8,2,5,13,12,3,9,4,7,1}, + {27,17,25,20,14,23,15,21,18,26,19,24,16,22,7,0,11,9,1,6,8,3,4,2,10,13,12,5}, + {27,17,25,14,23,19,22,20,18,16,26,24,21,15,13,9,0,5,10,7,4,8,2,12,6,11,3,1}, + {20,26,21,27,22,25,16,15,18,17,19,23,24,14,1,12,0,4,8,10,2,9,7,11,6,3,5,13}, + {23,27,19,15,26,22,17,16,14,18,21,25,24,20,12,1,0,9,11,2,8,10,13,7,4,6,3,5}, + {23,15,21,19,25,27,24,20,26,17,22,16,18,14,3,2,6,8,0,9,5,1,10,12,7,4,13,11}, + {26,18,27,20,24,17,23,25,21,19,16,22,14,15,12,1,10,7,2,5,3,13,11,8,4,9,6,0}, + {15,25,20,26,18,22,14,24,27,17,19,21,23,16,6,10,12,8,3,1,9,7,5,11,0,2,13,4}, + {27,20,24,15,23,21,19,14,22,26,16,18,17,25,13,7,11,1,12,4,9,8,0,6,5,10,3,2}, + {19,25,24,21,23,16,18,20,15,17,22,26,14,27,1,5,4,2,9,8,0,11,6,10,12,3,7,13}, + {14,25,24,15,21,17,19,16,23,22,27,26,18,20,5,1,6,11,0,3,12,9,7,2,4,10,8,13}, + {18,26,15,20,14,22,25,27,17,23,16,21,24,19,1,7,13,2,12,4,9,6,5,0,8,11,3,10}, + {18,17,23,25,14,21,19,27,26,24,15,20,22,16,10,0,7,9,5,1,12,11,8,2,4,6,13,3}, + {23,16,22,25,24,21,20,15,14,19,26,17,27,18,1,13,4,3,12,7,0,9,5,10,2,11,8,6}, + {25,15,23,17,22,19,16,26,20,24,27,21,14,18,13,9,2,6,8,0,12,7,1,5,11,4,10,3}, + {14,27,17,25,15,21,20,23,26,18,24,19,16,22,4,3,6,2,12,5,11,1,0,8,10,13,7,9}, + {25,17,27,14,18,16,20,19,23,26,21,15,22,24,12,0,7,2,10,9,11,5,8,6,1,4,3,13}, + {21,20,23,18,15,24,26,16,25,27,22,17,19,14,12,3,10,6,8,13,0,4,1,5,2,11,9,7}, + {15,17,19,27,20,14,24,18,23,16,25,21,26,22,1,5,9,13,4,2,7,11,0,8,12,6,10,3}, + {25,17,15,19,24,18,26,22,21,16,27,20,23,14,8,13,10,12,0,4,7,3,11,6,2,9,5,1}, + {15,17,26,23,20,27,18,24,16,21,22,25,19,14,11,13,8,3,6,12,0,4,10,9,1,7,2,5}, + {22,17,24,19,25,18,21,26,23,27,20,16,14,15,10,6,13,5,12,4,8,2,7,11,9,3,1,0}, + {19,25,23,18,22,24,16,20,17,26,14,21,15,27,3,1,6,12,9,7,0,10,4,13,8,5,2,11}, + {15,24,14,27,18,20,17,19,16,26,25,22,21,23,9,12,11,13,1,7,2,5,4,10,3,0,8,6}, + {21,26,15,19,22,17,14,23,24,20,27,16,18,25,13,4,1,12,7,11,10,6,3,9,2,8,5,0}, + {15,19,17,21,24,14,23,20,25,18,27,16,26,22,12,6,11,10,9,3,0,2,1,7,4,13,8,5}, + {19,17,20,18,23,15,26,25,27,16,21,24,22,14,8,6,1,3,7,4,9,13,12,10,0,2,11,5}, + {16,25,22,14,23,26,18,15,19,21,24,27,17,20,6,13,4,12,8,2,11,1,7,10,3,0,9,5}, + {25,22,21,16,26,24,20,19,18,23,15,27,14,17,0,9,11,10,8,2,12,6,4,13,7,3,1,5}, + {14,19,17,22,27,23,16,15,25,20,26,21,24,18,5,11,4,7,0,8,3,2,9,13,6,1,10,12}, + {23,20,21,24,27,16,25,22,18,14,19,15,26,17,9,7,12,2,8,6,1,4,11,0,10,3,5,13}, + {27,17,14,15,20,26,23,21,19,25,16,18,22,24,4,8,7,10,6,1,12,5,2,13,3,0,9,11}, + {21,26,25,15,17,24,23,27,22,18,16,20,19,14,3,11,0,9,6,10,4,2,8,12,7,5,13,1}, + {27,25,14,20,23,26,21,15,22,18,17,16,19,24,5,9,2,13,4,3,12,6,8,7,0,1,11,10}, + {26,14,25,17,24,22,20,19,18,16,21,15,23,27,1,11,2,12,5,4,0,7,6,8,10,3,9,13}, + {19,21,14,26,17,16,27,23,25,22,18,20,24,15,0,13,11,8,1,5,7,10,2,4,9,6,3,12}, + {24,20,15,23,25,17,22,14,16,18,27,19,26,21,11,9,13,10,2,7,6,8,1,3,0,4,12,5}, + {27,20,19,22,17,23,25,16,15,24,18,26,21,14,10,2,7,0,11,13,5,3,6,12,9,1,8,4}, + {18,22,20,27,17,26,15,23,16,14,21,19,25,24,13,8,6,10,1,5,3,7,11,0,9,4,12,2}, + {25,21,18,15,26,19,24,16,22,17,23,14,27,20,8,5,4,6,12,9,0,13,3,1,10,7,11,2}, + {25,19,22,21,26,17,16,15,18,27,23,24,20,14,10,5,12,2,8,4,11,1,13,7,9,0,6,3}, + {24,23,17,21,26,19,15,25,22,20,16,14,27,18,7,6,9,0,11,5,1,12,10,8,2,13,4,3}, + {24,22,17,26,18,14,27,21,15,25,23,20,19,16,4,2,10,3,11,1,5,7,12,8,0,13,6,9}, + {27,21,16,19,22,14,23,26,25,18,15,17,20,24,13,11,3,5,12,6,4,0,2,9,1,8,10,7}, + {14,24,27,15,22,19,20,18,26,21,23,17,25,16,9,12,2,11,3,5,1,0,13,4,8,7,6,10}, + {27,23,15,17,26,25,22,14,21,24,19,18,20,16,6,0,2,8,4,9,1,10,5,12,11,13,3,7}, + {22,25,18,24,26,14,16,23,27,19,15,20,21,17,13,0,4,1,3,8,12,6,9,7,5,10,2,11}, + {21,23,27,20,25,24,26,18,15,17,14,16,22,19,12,5,9,8,6,11,2,13,3,1,7,0,4,10}, + {16,21,14,24,18,27,25,22,20,19,15,23,17,26,0,7,11,8,2,5,3,12,1,10,9,13,6,4}, + {26,20,25,16,22,27,18,21,14,24,19,17,23,15,8,10,13,5,0,12,11,2,7,6,4,9,3,1}, + {20,22,15,24,17,26,21,27,19,23,18,16,25,14,2,4,0,8,1,13,5,10,3,6,9,12,7,11}, + {18,17,27,19,21,26,23,22,16,20,24,15,25,14,9,4,0,2,11,13,8,10,6,12,1,3,5,7}, + {21,27,14,17,25,23,16,22,24,26,15,20,19,18,8,12,0,11,10,2,1,13,7,5,6,3,9,4}, + {18,14,20,23,25,22,15,24,26,17,19,21,27,16,13,11,7,4,6,5,9,1,10,12,3,0,8,2}, + {25,17,21,20,23,27,18,26,24,15,19,22,16,14,4,3,0,2,5,12,7,6,9,8,11,1,10,13}, + {21,26,25,24,20,17,27,16,15,19,23,22,18,14,3,5,1,8,10,4,9,13,6,12,7,0,2,11}, + {26,21,27,22,16,18,25,23,14,20,15,24,17,19,8,10,5,12,11,13,2,4,3,7,1,6,0,9}, + {22,26,16,25,23,14,17,24,18,21,20,15,27,19,9,0,2,11,6,3,5,10,1,8,12,4,13,7}, + {18,16,19,25,21,26,14,22,17,20,27,24,23,15,1,5,12,11,8,2,3,7,10,6,4,9,0,13}, + {15,24,22,16,19,17,23,20,26,18,25,14,21,27,1,0,3,5,9,4,8,11,2,6,12,10,7,13}, + {26,16,24,21,25,17,23,20,27,19,15,18,14,22,2,9,6,8,11,13,4,1,10,5,3,12,7,0}, + {26,16,27,23,15,24,21,18,20,25,22,17,19,14,3,2,12,5,11,8,0,9,6,1,7,13,4,10}, + {16,22,17,24,27,23,26,19,25,21,18,14,20,15,0,11,5,4,9,1,3,10,6,12,8,2,13,7}, + {25,20,23,27,24,17,21,26,18,15,19,14,22,16,10,12,5,3,1,9,8,0,6,2,11,7,4,13}, + {27,14,24,23,20,15,16,18,22,26,21,19,17,25,1,7,4,6,8,3,13,12,11,10,2,0,5,9}, + {19,14,23,16,22,17,27,21,20,26,15,24,18,25,5,10,7,11,6,8,2,1,3,13,12,0,9,4}, + {24,21,26,18,25,23,15,17,19,22,27,20,16,14,2,1,13,8,0,4,11,5,10,6,12,9,7,3}, + {27,24,17,22,18,26,23,21,20,19,25,15,14,16,4,13,5,1,6,11,10,2,9,3,8,12,0,7}, + {17,24,14,18,22,20,27,16,26,19,25,15,21,23,5,8,11,7,12,2,1,9,4,6,3,0,10,13}, + {23,17,20,26,21,16,25,24,15,14,27,19,22,18,0,8,5,11,13,2,9,3,12,4,7,1,6,10}, + {25,22,17,23,14,19,21,15,18,20,27,24,26,16,7,12,6,10,3,11,1,8,5,0,13,9,2,4}, + {22,19,27,23,14,26,16,25,15,18,24,17,21,20,9,5,12,0,3,11,1,10,4,8,6,2,7,13}, + {18,15,19,24,17,16,21,26,20,23,22,25,27,14,8,12,5,13,6,0,9,1,11,4,3,7,10,2}, + {15,19,21,25,16,18,22,26,17,24,14,27,20,23,9,7,5,3,13,8,12,2,6,10,0,1,4,11}, + {26,17,22,27,15,18,16,20,25,19,23,21,14,24,0,4,11,5,7,9,1,8,6,2,12,10,3,13}, + {21,19,18,15,22,25,23,26,16,20,17,27,14,24,2,9,6,10,4,11,5,12,8,3,7,1,13,0}, + } + }, + { 29, { + {19,14,26,28,16,27,15,24,22,17,25,21,20,18,23,6,4,10,2,7,13,11,8,1,0,9,12,5,3}, + {14,24,20,25,15,27,19,17,21,23,28,18,26,16,22,8,1,7,6,9,4,3,0,5,11,13,2,10,12}, + {18,27,21,25,28,22,19,15,26,16,23,20,24,17,14,3,9,6,0,8,7,5,4,10,12,2,11,1,13}, + {15,18,27,24,21,26,23,25,17,19,28,14,22,20,16,8,2,9,4,10,6,1,7,12,0,11,13,5,3}, + {28,20,16,21,27,24,26,23,22,18,25,17,19,15,10,5,1,14,11,4,9,6,12,3,2,0,7,13,8}, + {19,24,21,20,28,16,18,26,22,23,15,17,25,27,8,10,3,1,6,4,11,2,14,0,7,12,5,13,9}, + {22,28,25,15,23,17,20,16,21,24,19,27,18,26,13,5,2,6,3,7,12,8,14,9,11,10,1,0,4}, + {18,14,26,22,28,27,17,21,20,24,19,16,25,23,15,3,12,1,10,13,5,7,0,11,9,6,8,4,2}, + {20,17,19,25,15,23,22,18,16,24,26,21,27,28,6,14,9,1,11,0,12,5,13,10,8,3,7,4,2}, + {19,24,17,15,28,16,26,21,18,20,23,25,22,27,5,3,14,7,8,0,9,2,12,10,1,11,6,13,4}, + {15,19,24,20,14,17,28,22,25,18,23,16,27,26,21,13,7,10,4,12,3,0,6,1,8,5,9,11,2}, + {18,15,20,16,21,24,17,27,22,26,28,25,19,23,5,1,3,12,11,6,2,14,8,13,9,7,4,10,0}, + {19,18,25,22,24,27,26,21,28,15,17,20,16,23,13,4,0,12,9,7,6,14,8,2,11,3,5,1,10}, + {17,27,21,24,28,22,14,19,18,23,26,25,16,20,15,12,6,10,3,0,9,8,11,5,1,7,13,4,2}, + {21,26,17,23,22,16,19,18,28,27,25,24,20,15,6,1,9,7,12,5,8,0,13,2,10,4,3,14,11}, + {20,27,15,25,23,28,21,24,22,18,17,19,26,16,1,12,11,8,3,9,6,5,4,7,13,0,14,10,2}, + {19,24,17,21,28,16,26,25,18,27,22,20,23,15,1,11,5,7,9,3,0,10,4,6,2,13,8,12,14}, + {18,20,17,23,22,28,16,25,27,24,26,21,15,19,7,0,11,2,9,13,1,10,14,5,12,8,4,6,3}, + {22,20,17,26,19,18,25,14,16,27,24,21,23,15,28,1,8,11,13,3,6,7,2,5,10,4,12,0,9}, + {16,18,25,20,22,26,28,23,17,24,14,27,15,21,19,12,5,8,1,10,3,0,4,2,9,7,13,11,6}, + {15,24,26,18,16,20,22,28,17,23,21,19,25,27,11,0,14,9,3,1,5,7,10,6,4,12,2,8,13}, + {17,16,15,27,25,19,21,18,26,24,23,28,20,22,13,10,4,3,0,8,7,1,12,2,6,9,5,14,11}, + {25,24,22,18,23,20,26,19,16,15,21,27,17,14,28,2,1,5,9,7,11,10,12,13,0,3,8,6,4}, + {19,16,26,25,24,21,23,18,15,20,27,17,22,28,13,3,9,6,0,5,11,4,12,1,7,2,14,8,10}, + {22,15,19,28,21,17,24,26,20,27,23,16,18,14,25,1,11,9,13,2,8,4,0,12,5,10,7,6,3}, + {20,24,17,23,22,28,25,16,14,19,26,18,27,21,15,6,4,13,8,0,9,11,3,5,1,7,10,12,2}, + {26,28,17,23,15,27,21,18,14,25,22,24,20,19,16,10,6,2,1,8,11,3,12,5,4,9,0,13,7}, + {26,16,15,27,19,17,25,21,28,20,22,24,18,23,11,13,1,8,0,4,6,14,3,12,7,2,10,9,5}, + {23,21,26,19,25,28,16,18,15,20,22,27,17,24,12,11,4,0,10,3,8,2,9,1,5,7,6,13,14}, + {20,17,25,28,27,26,24,16,21,19,18,14,15,23,22,2,7,4,10,1,0,8,6,3,13,12,5,9,11}, + {17,19,24,15,21,23,26,20,22,16,14,18,28,27,25,0,9,12,6,5,1,3,13,4,10,8,7,2,11}, + {23,16,19,22,28,24,18,26,25,20,27,15,17,21,13,11,1,6,12,8,5,10,3,0,9,4,7,14,2}, + {18,24,23,20,22,21,28,19,17,16,27,15,26,14,25,11,10,5,4,7,0,8,12,1,3,2,9,6,13}, + {22,17,23,25,18,21,27,19,24,26,20,16,15,14,28,3,0,11,6,12,8,13,10,7,4,1,9,2,5}, + {21,16,27,18,25,15,24,19,26,22,28,23,17,20,1,12,7,10,8,2,14,13,11,4,0,6,9,5,3}, + {16,22,18,24,27,20,15,28,17,26,23,25,19,21,3,10,8,11,14,9,5,12,6,2,13,0,7,1,4}, + {21,18,20,16,15,26,17,19,25,28,23,22,24,27,11,6,14,13,2,9,4,8,12,10,3,7,0,5,1}, + {22,27,28,17,23,25,20,26,16,18,24,21,15,19,8,0,10,13,9,11,6,12,3,1,14,5,7,4,2}, + {20,23,15,24,21,26,19,27,22,18,16,17,28,25,4,8,13,1,6,5,12,10,14,3,7,9,2,0,11}, + {28,22,24,16,18,25,20,17,26,19,23,15,21,27,6,12,4,0,7,2,1,3,8,5,10,13,11,14,9}, + {22,28,15,20,16,25,24,26,19,27,21,18,17,23,8,14,3,10,7,9,4,0,13,11,6,12,5,2,1}, + {19,21,28,26,22,25,16,15,23,20,27,18,14,17,24,5,6,3,1,0,4,11,7,13,9,12,8,10,2}, + {18,15,20,26,24,17,22,25,23,19,16,28,21,27,13,7,5,11,6,0,9,12,4,3,1,8,10,2,14}, + {16,20,23,22,27,21,19,14,17,24,18,25,28,26,15,0,12,10,4,8,11,13,9,2,6,3,1,7,5}, + {23,25,28,17,21,26,24,27,18,22,20,16,14,19,15,9,1,8,12,2,10,7,3,6,0,5,4,11,13}, + {14,15,24,17,20,26,25,18,21,16,19,28,23,27,22,12,8,5,7,1,3,6,0,2,4,13,9,11,10}, + {20,17,16,21,25,15,28,19,26,23,22,27,24,18,5,14,7,10,1,4,3,12,9,11,2,0,6,13,8}, + {27,15,28,24,19,21,22,25,23,18,17,26,20,16,10,12,3,9,2,7,1,0,13,8,5,4,6,14,11}, + {21,19,23,25,16,24,17,22,20,14,18,28,15,27,26,4,12,1,9,7,6,0,11,8,5,3,10,13,2}, + {15,18,28,23,25,21,20,27,17,19,22,14,16,26,24,11,1,3,10,8,5,9,7,13,6,4,2,12,0}, + {28,23,19,26,14,16,27,22,18,25,17,15,21,24,20,12,5,10,8,2,7,3,4,9,0,6,11,1,13}, + {20,26,28,24,19,23,25,17,15,21,27,18,22,16,1,7,4,8,2,14,9,12,11,10,13,6,0,3,5}, + {26,19,25,22,20,16,23,18,28,27,24,17,21,15,3,10,14,13,9,11,5,2,12,1,8,6,4,0,7}, + {26,19,24,22,16,20,23,25,15,21,28,27,18,17,5,8,0,4,14,7,3,10,13,1,6,11,9,2,12}, + {20,16,22,18,27,23,19,21,26,15,17,28,24,14,25,9,5,11,13,0,8,6,1,3,7,2,10,12,4}, + {26,17,28,21,15,20,25,18,24,23,22,27,16,19,0,2,13,9,3,5,10,7,1,8,11,4,12,6,14}, + {18,14,20,17,21,16,28,26,24,23,19,25,27,22,15,6,4,11,1,0,9,7,2,10,8,3,12,5,13}, + {28,18,26,24,21,27,20,16,25,23,19,22,15,17,9,11,1,0,8,3,2,5,13,6,10,12,14,4,7}, + {25,24,18,23,27,21,26,15,19,17,22,16,20,28,0,4,7,12,14,13,1,6,10,3,5,2,9,11,8}, + {21,20,22,26,18,14,19,16,23,25,17,27,24,28,15,4,12,9,13,6,2,11,10,0,7,5,1,3,8}, + {27,24,28,17,23,26,16,25,20,18,21,19,22,15,6,3,5,9,0,8,4,12,1,13,2,10,14,11,7}, + {20,15,22,18,26,24,27,23,17,28,16,25,19,21,13,12,10,5,0,2,1,3,7,11,14,9,6,4,8}, + {23,25,28,18,26,14,21,27,17,24,15,20,19,16,22,7,4,6,2,1,11,13,5,12,0,10,8,3,9}, + {16,18,27,26,28,19,22,20,24,17,21,23,15,25,5,11,4,7,12,14,10,2,0,9,6,8,13,3,1}, + {28,19,18,14,20,23,27,16,25,21,26,15,22,17,24,6,2,4,9,10,5,12,7,13,3,0,8,11,1}, + {25,16,18,15,22,14,17,21,27,24,23,19,26,28,20,11,9,8,0,3,5,10,13,6,2,4,12,1,7}, + {24,20,16,26,25,22,28,15,17,23,19,18,21,27,5,14,13,10,6,0,9,12,4,2,8,1,3,11,7}, + {16,18,14,17,25,21,24,20,26,28,19,15,23,22,27,9,0,7,13,2,1,3,11,12,6,4,8,10,5}, + {18,24,28,14,26,20,16,27,22,17,15,25,19,21,23,9,8,7,0,10,12,3,11,13,4,6,1,5,2}, + {21,28,15,19,27,24,17,25,20,22,14,23,18,16,26,11,1,8,12,3,5,0,6,2,10,13,9,4,7}, + {27,18,16,26,23,25,15,28,24,22,21,17,20,19,5,11,8,13,1,10,12,3,9,4,6,2,14,0,7}, + {15,26,17,24,21,19,22,14,16,25,18,27,20,23,28,9,12,5,10,7,11,0,8,13,1,3,2,4,6}, + {23,16,19,25,22,18,17,15,20,28,26,21,24,27,5,8,11,2,1,9,13,0,14,7,12,3,10,4,6}, + {25,23,26,21,27,15,22,17,16,18,28,19,20,24,2,5,11,6,9,8,0,7,13,12,3,14,10,4,1}, + {14,22,16,27,25,17,23,19,28,24,18,21,26,20,15,13,11,9,8,7,3,6,0,2,5,1,12,4,10}, + {19,21,27,26,15,22,14,18,17,20,24,28,23,25,16,10,0,6,3,11,7,2,4,9,8,5,13,12,1}, + {20,28,18,14,17,24,15,25,16,27,23,19,22,21,26,12,9,4,10,3,5,13,0,2,6,8,11,7,1}, + {28,25,17,24,20,26,19,21,27,22,16,18,23,15,6,12,5,9,0,4,14,2,8,7,13,10,3,1,11}, + {16,28,18,15,17,21,19,22,25,24,26,20,14,23,27,3,0,12,4,6,8,13,10,1,7,5,9,11,2}, + {24,16,15,14,27,25,17,19,26,23,22,20,18,21,28,12,9,5,10,0,2,11,3,8,7,13,4,1,6}, + {25,15,17,19,21,23,18,24,22,20,28,16,27,26,10,13,0,14,7,4,8,11,9,2,6,12,5,3,1}, + {27,16,22,19,23,21,18,14,24,26,28,20,15,17,25,3,1,13,6,2,11,5,0,4,8,7,12,9,10}, + {17,27,22,28,25,24,26,23,15,20,18,21,19,16,11,8,13,6,9,1,10,14,2,4,12,7,0,5,3}, + {16,27,21,15,26,28,25,23,17,22,24,19,18,14,20,6,13,11,2,9,3,10,4,7,0,8,5,1,12}, + {26,23,15,24,20,14,22,27,16,19,21,18,25,28,17,2,6,5,3,1,11,8,13,9,12,7,0,4,10}, + {17,27,15,18,28,25,24,16,21,26,22,20,14,19,23,7,11,6,0,3,12,10,8,4,2,9,13,1,5}, + {14,21,15,28,23,20,16,19,18,25,22,26,17,27,24,10,3,7,12,5,4,8,1,0,13,6,9,2,11}, + {15,27,24,20,17,22,28,16,19,25,18,23,26,21,14,0,2,4,10,5,3,13,8,11,9,7,12,1,6}, + {24,18,26,28,16,20,19,27,21,25,23,15,14,17,22,11,3,5,7,0,8,1,4,13,10,9,12,6,2}, + {15,14,24,16,18,21,23,26,20,22,28,17,27,19,25,0,3,5,7,6,4,10,9,12,11,1,13,8,2}, + {23,17,26,18,15,20,24,19,25,21,28,22,27,16,1,3,14,9,4,10,6,13,12,5,8,2,0,7,11}, + {24,28,18,22,27,17,20,16,23,21,15,25,14,26,19,1,3,5,2,10,6,9,12,7,4,11,13,0,8}, + {16,23,18,22,17,20,15,25,28,21,14,19,26,24,27,1,0,4,2,11,5,9,3,6,13,7,12,8,10}, + {17,14,18,27,16,28,20,26,23,15,22,24,21,19,25,13,9,2,6,11,7,3,8,0,10,5,4,1,12}, + {21,15,26,28,27,24,18,25,19,17,16,20,22,23,0,2,4,7,5,8,11,14,12,1,6,9,13,10,3}, + {16,22,21,23,25,24,18,19,27,17,15,26,20,28,3,10,13,8,7,9,4,11,6,1,5,2,0,14,12}, + {21,26,17,22,24,28,16,27,20,25,19,15,23,18,10,14,0,11,3,7,5,9,4,13,2,6,12,1,8}, + {16,21,15,22,17,25,18,19,28,24,20,26,23,27,6,14,13,12,2,9,8,7,11,4,1,0,5,10,3}, + {23,26,15,18,19,22,25,28,24,21,27,17,20,16,2,5,3,11,0,13,12,1,8,10,14,9,6,4,7}, + {23,21,28,19,14,20,15,18,25,27,17,26,22,24,16,13,4,10,7,11,2,1,6,5,8,12,3,9,0}, + } + }, + { 30, { + {25,15,27,29,19,28,24,16,20,22,26,23,21,17,18,11,4,9,14,7,6,2,13,1,0,5,10,8,12,3}, + {29,27,18,17,19,28,26,25,23,21,16,22,20,15,24,14,9,13,4,6,5,2,0,12,8,1,7,11,10,3}, + {27,21,23,18,24,17,28,26,22,29,20,19,16,15,25,3,9,1,14,5,12,4,8,6,0,11,13,10,7,2}, + {25,28,17,22,21,27,24,29,26,16,19,15,18,20,23,4,10,5,2,0,9,6,14,7,1,11,3,13,12,8}, + {28,18,27,16,25,15,29,21,20,22,19,17,23,26,24,9,10,14,12,5,8,0,4,11,2,13,7,3,6,1}, + {26,17,15,25,28,22,27,16,23,21,18,29,19,24,20,11,3,1,10,8,13,7,5,12,9,14,0,2,4,6}, + {21,23,29,15,20,27,26,28,24,19,22,17,25,18,16,3,8,5,11,7,12,0,10,2,13,9,1,4,14,6}, + {26,17,19,25,22,29,28,15,21,27,16,24,18,23,20,12,1,8,4,13,3,10,7,0,11,9,6,14,2,5}, + {21,16,23,26,28,25,17,27,19,29,24,18,22,20,15,14,11,8,10,12,2,13,7,6,5,9,3,0,1,4}, + {23,16,20,22,15,27,26,29,24,19,28,21,18,25,17,6,5,1,12,0,11,7,3,9,8,13,2,10,14,4}, + {18,20,26,17,25,21,19,24,27,16,28,23,29,15,22,5,9,2,11,8,0,14,10,1,3,13,4,6,12,7}, + {26,21,28,27,23,25,24,18,22,19,16,20,29,17,15,4,8,7,2,10,1,3,14,0,11,6,13,12,5,9}, + {26,22,25,23,28,21,29,27,19,15,18,16,20,24,17,6,10,13,14,0,2,8,1,4,7,12,11,5,9,3}, + {26,22,24,28,23,27,25,16,20,15,21,19,29,18,17,2,8,11,10,7,1,9,4,6,14,3,0,5,12,13}, + {26,27,17,22,29,25,15,18,23,28,16,20,24,19,21,13,1,7,12,8,0,14,4,6,2,11,5,10,3,9}, + {28,20,18,25,22,24,29,17,19,16,26,15,21,27,23,11,3,7,2,10,4,12,5,0,13,14,8,1,6,9}, + {16,26,18,22,25,19,29,17,27,21,20,24,23,28,15,14,6,11,8,10,9,0,12,2,7,3,1,4,13,5}, + {27,16,15,21,20,28,25,17,26,23,29,22,19,24,18,13,0,10,12,1,9,5,2,8,4,3,14,7,11,6}, + {17,26,15,29,23,19,28,20,25,27,22,16,24,18,21,11,14,7,13,12,3,8,10,4,5,2,1,9,6,0}, + {15,24,28,18,16,21,29,23,26,17,20,19,25,27,22,14,2,8,4,9,11,0,10,13,1,7,3,5,12,6}, + {28,21,27,20,25,24,18,29,17,22,19,16,26,23,15,13,4,12,9,8,0,11,14,2,1,7,5,10,6,3}, + {21,23,19,25,15,29,24,18,20,26,28,22,17,27,16,0,13,11,7,2,9,8,14,12,6,3,5,10,1,4}, + {26,16,24,21,17,28,22,15,23,20,27,29,18,25,19,14,11,5,10,2,12,3,6,0,7,4,8,9,13,1}, + {24,21,19,26,28,25,18,29,27,23,16,15,20,22,17,10,9,4,6,3,13,5,8,7,2,12,14,11,0,1}, + {28,20,23,22,24,17,16,15,25,27,19,26,29,18,21,9,14,10,12,6,13,0,5,4,8,2,1,3,11,7}, + {27,22,16,19,28,21,17,25,23,18,20,26,24,29,15,7,5,9,4,2,0,12,11,6,1,14,13,3,10,8}, + {17,15,21,23,20,27,18,24,19,16,25,26,22,29,28,10,8,11,6,12,4,7,9,2,1,3,0,5,14,13}, + {19,22,18,29,25,27,23,15,24,16,26,28,21,20,17,1,11,5,9,0,7,4,13,6,8,12,10,3,2,14}, + {28,27,29,21,24,23,26,18,22,25,20,19,17,15,16,3,6,10,1,11,2,4,8,5,13,9,0,7,14,12}, + {26,18,24,17,27,15,29,28,16,19,22,20,25,21,23,8,5,4,2,11,0,3,9,7,10,12,6,13,14,1}, + {24,27,29,20,18,15,23,16,26,21,19,22,28,17,25,3,1,7,4,13,0,5,2,10,8,12,14,11,9,6}, + {15,28,27,29,18,23,19,16,26,22,21,25,17,20,24,5,9,8,11,2,1,4,7,13,12,14,10,0,3,6}, + {25,24,17,21,28,16,18,26,20,27,29,23,15,22,19,6,0,12,3,11,8,14,7,9,10,13,5,2,1,4}, + {25,20,22,16,28,19,27,29,26,23,17,21,18,24,15,8,11,9,14,12,6,4,13,0,7,5,1,3,2,10}, + {29,28,18,26,20,25,15,23,27,24,21,19,17,22,16,12,10,9,1,8,3,14,2,5,7,0,11,13,6,4}, + {23,29,19,18,27,24,15,17,20,25,22,26,28,16,21,14,12,0,4,3,7,6,13,10,9,5,8,1,11,2}, + {16,17,15,29,21,23,20,19,18,27,26,25,28,24,22,8,1,4,14,5,7,6,13,12,11,3,9,2,10,0}, + {20,22,16,29,26,23,18,25,27,24,17,21,28,19,15,14,8,2,11,1,13,7,10,3,0,12,4,9,6,5}, + {23,25,28,24,19,27,29,17,26,16,21,15,20,18,22,0,14,6,9,4,2,1,5,12,11,8,7,3,10,13}, + {16,18,27,23,15,28,17,20,24,29,22,25,21,26,19,14,4,13,2,10,7,0,3,9,1,6,5,11,8,12}, + {20,27,19,22,15,24,18,21,16,26,25,29,23,17,28,4,11,9,8,3,10,14,5,2,1,6,13,7,12,0}, + {27,24,18,23,20,19,17,15,25,16,28,22,21,26,29,13,0,2,8,5,10,1,11,3,7,14,4,6,9,12}, + {24,17,20,25,23,28,18,27,21,29,19,16,26,22,15,0,4,7,2,9,10,3,1,6,11,8,13,12,14,5}, + {16,18,20,15,21,24,22,28,26,25,17,23,19,29,27,3,2,10,14,12,9,4,1,11,6,0,8,5,7,13}, + {15,23,20,29,26,17,19,21,27,24,18,16,22,25,28,12,7,6,1,14,11,2,9,5,8,13,3,0,4,10}, + {29,20,28,22,18,15,24,21,19,27,17,25,16,23,26,5,1,9,4,8,3,14,13,10,2,6,0,7,11,12}, + {17,28,27,24,21,23,18,25,19,15,22,26,20,29,16,2,1,8,6,0,12,11,7,10,3,9,4,14,5,13}, + {24,20,18,22,27,19,26,28,25,29,23,17,21,16,15,3,5,11,9,13,10,6,8,0,2,14,1,4,7,12}, + {23,18,25,21,15,26,17,16,20,29,27,22,19,24,28,4,0,2,12,6,8,11,10,14,13,3,7,1,9,5}, + {18,26,22,16,25,17,20,28,24,27,23,21,19,29,15,8,5,13,9,11,10,3,6,12,1,4,0,7,14,2}, + {25,29,22,15,27,20,26,24,19,18,17,16,28,23,21,2,7,12,11,3,5,4,8,6,10,14,1,13,0,9}, + {24,19,21,18,28,22,26,16,25,23,17,15,29,27,20,11,14,3,1,8,5,12,2,7,6,4,10,9,13,0}, + {29,18,15,20,27,17,22,24,21,25,28,19,26,23,16,10,2,11,9,13,6,8,0,5,12,3,7,1,4,14}, + {18,24,20,19,26,23,27,22,15,17,25,21,29,16,28,10,3,7,11,4,6,0,2,5,12,9,8,14,1,13}, + {29,26,18,25,15,28,20,27,17,24,21,23,19,16,22,1,12,5,3,2,6,10,0,11,9,7,4,8,14,13}, + {23,21,28,18,26,15,20,24,16,25,19,22,17,29,27,6,9,11,1,12,4,2,7,13,5,8,14,3,0,10}, + {21,24,28,19,26,29,16,18,15,17,20,22,25,23,27,8,10,3,4,9,6,11,7,2,1,12,0,14,13,5}, + {22,15,19,17,25,29,24,26,16,21,28,20,27,18,23,12,4,9,6,1,13,3,0,10,2,7,14,11,8,5}, + {23,16,25,21,17,29,15,18,28,19,27,24,20,22,26,3,14,10,0,9,12,13,6,11,7,5,1,4,8,2}, + {20,17,25,28,15,24,18,26,29,22,21,19,27,23,16,6,9,7,2,11,14,3,8,1,5,0,12,4,13,10}, + {22,26,29,24,27,25,16,19,17,15,28,20,23,21,18,8,3,1,12,10,13,2,9,14,11,4,0,6,5,7}, + {15,24,20,23,25,21,29,18,28,27,17,22,16,19,26,12,8,10,2,4,6,14,5,3,13,9,1,11,0,7}, + {24,23,22,21,20,28,29,25,16,26,17,19,15,18,27,0,8,14,4,9,2,11,7,6,13,12,5,10,3,1}, + {23,22,19,29,18,15,17,21,28,20,25,27,26,24,16,9,8,0,5,7,14,11,13,10,2,12,4,1,6,3}, + {28,18,15,17,25,24,20,22,19,27,23,29,21,26,16,8,13,3,5,10,9,7,12,1,4,11,6,2,14,0}, + {27,18,23,20,29,16,15,17,28,25,21,24,19,22,26,10,8,13,14,12,7,6,2,9,1,3,5,0,11,4}, + {22,27,16,26,18,28,15,17,19,21,25,23,20,29,24,0,2,8,3,10,7,5,4,6,11,14,12,9,1,13}, + {19,18,20,27,15,29,17,28,23,16,26,25,21,24,22,11,1,7,2,4,0,3,13,5,10,14,9,6,8,12}, + {17,23,15,19,27,18,26,24,21,16,25,22,20,29,28,14,13,7,5,3,12,4,11,9,0,6,2,8,10,1}, + {19,15,24,28,27,22,29,26,17,25,23,20,18,21,16,10,13,2,7,11,6,5,9,0,12,1,3,14,8,4}, + {26,16,22,20,15,18,17,21,23,19,24,28,25,29,27,13,12,6,9,11,5,10,2,8,7,3,0,14,1,4}, + {27,22,28,18,15,19,16,23,20,29,17,21,26,25,24,1,6,14,4,9,0,11,8,13,12,3,5,10,2,7}, + {15,24,29,25,21,16,28,23,26,22,17,20,18,27,19,12,13,3,11,10,9,6,1,5,8,4,7,0,2,14}, + {17,22,20,29,18,26,21,24,28,25,23,16,27,15,19,13,10,7,5,12,14,1,0,2,11,6,3,8,4,9}, + {23,19,26,17,25,20,18,21,24,15,22,27,29,28,16,1,11,6,4,9,13,10,2,0,12,7,14,3,8,5}, + {18,17,15,22,24,20,29,27,23,21,26,28,25,19,16,6,2,4,11,9,7,12,1,14,8,13,5,3,10,0}, + {17,22,16,29,21,23,25,20,26,28,27,19,15,18,24,5,0,2,11,8,10,4,13,14,6,12,9,1,3,7}, + {16,21,25,22,24,15,29,20,26,19,17,28,18,23,27,14,5,3,7,12,8,4,10,1,13,0,6,11,2,9}, + {26,17,24,22,19,27,25,23,16,21,29,20,15,18,28,13,8,1,11,7,6,12,14,5,10,9,3,2,0,4}, + {23,15,26,29,25,17,22,18,24,19,16,27,20,28,21,14,9,12,0,2,8,13,11,1,5,10,3,7,6,4}, + {15,28,25,17,29,22,20,19,24,26,23,21,27,18,16,0,2,4,12,7,3,14,6,10,8,11,13,9,1,5}, + {26,16,17,19,29,21,25,20,15,28,27,23,22,24,18,14,8,3,12,2,9,7,5,0,6,13,4,10,1,11}, + {26,23,22,17,29,16,21,19,27,24,18,28,20,15,25,14,5,3,6,4,13,0,2,1,7,12,10,8,9,11}, + {26,22,29,15,19,25,27,16,17,28,24,21,23,20,18,3,13,7,4,14,2,5,11,0,10,1,12,6,9,8}, + {27,26,28,24,22,19,16,29,17,20,25,23,15,21,18,13,12,5,11,3,14,8,4,9,6,10,1,0,2,7}, + {21,16,27,26,18,24,23,29,22,19,28,25,17,15,20,7,13,2,9,12,10,14,8,6,5,1,4,11,0,3}, + {15,18,27,20,25,23,29,19,24,28,16,26,21,17,22,1,10,12,14,6,9,11,7,3,5,13,0,2,8,4}, + {16,27,23,19,17,29,18,26,24,28,22,21,25,15,20,1,10,2,12,8,3,6,11,14,0,4,7,9,5,13}, + {26,17,22,20,24,15,18,16,28,27,29,19,25,23,21,5,13,3,14,10,8,0,7,1,6,11,9,12,2,4}, + {21,23,25,18,16,22,17,28,24,27,20,26,19,29,15,1,6,8,14,12,9,13,0,4,2,7,5,11,3,10}, + {27,17,28,21,26,20,15,24,22,25,29,19,16,23,18,6,9,8,12,11,3,1,0,2,14,5,4,7,13,10}, + {19,28,23,26,16,21,27,22,29,15,18,25,20,24,17,5,12,1,14,6,11,3,0,2,9,4,10,7,13,8}, + {26,22,21,15,24,28,27,23,16,20,25,18,17,29,19,12,13,11,4,0,2,8,1,3,5,7,9,6,10,14}, + {25,27,21,16,19,29,22,28,24,18,26,17,15,23,20,12,4,10,13,6,9,11,0,3,1,8,7,14,2,5}, + {22,20,25,18,26,19,29,28,23,24,17,16,27,21,15,13,5,14,3,11,10,12,6,1,4,2,9,8,7,0}, + {29,28,20,23,16,21,26,17,25,19,15,27,18,24,22,14,7,2,13,0,12,10,5,11,9,6,4,3,1,8}, + {24,28,23,26,21,17,25,20,29,16,19,27,22,18,15,3,11,4,6,0,12,1,9,13,5,2,8,7,10,14}, + {17,27,23,29,21,16,28,22,24,20,26,15,18,25,19,11,4,14,7,2,0,9,5,8,3,6,12,10,1,13}, + {16,18,23,27,26,17,15,25,21,20,28,24,19,22,29,0,4,5,14,9,3,8,13,7,1,12,11,2,10,6}, + {17,19,24,22,15,26,21,29,25,23,18,27,16,28,20,14,12,8,1,0,13,10,2,5,7,9,6,4,11,3}, + } + }, + { 31, { + {19,30,27,29,20,28,21,18,25,22,26,24,15,17,16,23,4,9,5,10,7,2,11,1,3,13,0,8,14,12,6}, + {23,25,17,28,27,29,26,19,15,22,18,16,20,24,30,21,3,2,10,5,1,4,6,0,7,14,12,13,8,11,9}, + {28,19,30,26,20,17,21,29,22,27,24,23,16,18,25,2,14,12,8,3,11,1,5,7,6,4,9,13,0,15,10}, + {21,15,29,27,30,17,16,19,28,25,22,24,18,20,26,23,11,0,12,9,4,13,6,1,10,7,14,3,8,2,5}, + {28,27,15,20,24,26,30,17,19,18,16,29,22,21,25,23,10,0,5,14,13,11,12,2,4,8,6,1,7,3,9}, + {25,22,21,18,24,23,29,27,19,26,16,28,30,17,20,5,2,11,6,12,10,13,1,9,4,14,7,0,8,15,3}, + {24,30,17,22,16,27,15,18,26,20,23,29,21,25,28,19,7,11,13,6,1,9,0,10,14,5,3,8,4,2,12}, + {30,16,20,24,22,18,25,29,17,21,23,28,27,26,19,1,3,10,5,13,15,11,8,14,7,12,9,6,2,4,0}, + {25,30,27,22,17,16,23,19,18,20,15,28,21,24,29,26,10,6,1,0,8,12,3,11,7,13,5,2,4,14,9}, + {29,18,25,28,17,16,24,19,21,20,27,23,22,30,15,26,13,9,12,7,0,10,6,14,4,2,11,1,3,5,8}, + {15,17,25,22,21,16,29,28,27,20,23,18,30,24,19,26,12,4,11,2,13,5,3,9,1,7,0,8,14,10,6}, + {26,30,18,16,28,23,17,19,27,29,24,22,20,25,21,14,5,8,6,1,12,10,3,2,15,13,4,0,7,11,9}, + {23,15,20,27,22,17,16,21,26,19,28,24,30,29,18,25,7,10,0,14,6,13,1,3,8,5,12,4,9,11,2}, + {28,17,24,16,23,27,21,18,25,19,22,30,29,26,20,1,9,4,2,12,0,7,6,8,14,13,3,15,10,5,11}, + {27,22,18,20,17,16,21,26,28,15,23,25,30,29,24,19,7,12,1,4,6,5,11,14,13,3,2,10,0,9,8}, + {19,23,25,20,18,30,27,22,24,26,15,28,17,21,16,29,6,8,10,3,7,12,0,14,9,13,2,1,5,4,11}, + {18,28,26,24,22,23,19,16,30,27,29,21,17,20,25,14,13,15,1,6,9,0,11,5,10,7,2,4,12,3,8}, + {17,25,20,24,19,22,28,27,15,23,29,26,18,21,30,16,10,1,11,7,9,14,5,8,6,13,3,2,12,4,0}, + {24,18,21,15,20,19,26,16,27,29,25,23,22,17,28,30,2,9,13,11,12,5,0,7,4,14,6,8,10,1,3}, + {16,23,19,15,20,22,28,17,30,26,29,27,24,21,18,25,9,3,1,4,6,5,7,11,8,14,0,2,13,12,10}, + {16,28,30,17,24,20,22,26,23,21,18,19,27,29,25,11,0,6,15,7,4,12,5,9,3,13,2,10,14,8,1}, + {17,28,30,19,27,16,22,20,23,21,24,29,25,18,15,26,11,13,4,7,1,10,6,8,12,0,3,2,9,5,14}, + {25,30,28,23,26,20,15,18,24,21,17,27,19,29,22,16,12,8,2,0,7,1,4,3,9,11,5,14,10,6,13}, + {23,21,28,16,20,15,17,29,19,24,18,30,27,26,22,25,3,6,1,11,10,0,4,12,8,2,14,7,13,5,9}, + {29,16,25,27,22,17,19,21,23,20,18,24,26,30,28,8,11,3,14,0,7,12,5,4,9,1,15,13,10,2,6}, + {26,28,18,30,29,16,19,22,24,23,27,20,21,17,15,25,5,12,2,0,11,8,1,9,7,3,6,10,13,4,14}, + {18,28,22,30,20,17,24,27,25,29,16,15,23,21,19,26,14,0,13,1,4,2,10,6,12,8,3,9,5,7,11}, + {23,20,22,27,25,21,16,24,30,26,17,19,28,18,29,3,15,0,2,11,7,1,14,13,4,6,9,12,5,10,8}, + {19,27,22,24,16,29,21,25,20,17,26,18,23,28,30,0,14,9,3,8,15,6,7,12,11,2,4,1,13,5,10}, + {20,24,18,21,23,27,29,26,22,15,25,17,28,19,30,16,2,13,9,8,4,11,6,3,12,14,0,10,7,5,1}, + {26,28,18,30,23,21,27,22,17,25,29,19,16,24,15,20,12,0,2,8,3,5,7,10,6,9,13,11,1,4,14}, + {25,21,16,19,28,20,26,18,30,15,27,17,29,23,22,24,13,7,0,2,5,1,9,8,11,14,10,12,4,3,6}, + {30,24,27,20,16,21,23,26,19,29,28,25,18,22,17,3,12,1,5,9,0,11,13,4,7,6,14,2,10,8,15}, + {21,24,22,25,30,23,18,20,29,28,26,19,15,17,16,27,10,0,9,3,2,6,8,5,1,11,4,7,12,14,13}, + {28,18,23,27,21,16,19,25,20,22,30,26,24,29,17,9,1,8,7,4,12,5,15,3,10,14,11,2,13,0,6}, + {20,30,18,19,26,15,25,16,22,28,27,23,29,21,24,17,14,2,7,1,9,6,10,4,8,5,11,0,13,3,12}, + {18,30,20,28,16,27,21,19,17,23,26,22,25,29,24,9,15,12,3,7,0,10,4,14,1,5,2,11,6,13,8}, + {18,16,26,25,21,27,17,28,19,22,15,29,23,20,30,24,7,2,11,8,6,14,12,0,9,10,13,5,4,1,3}, + {16,26,20,18,29,25,15,24,28,17,22,21,30,27,23,19,2,10,3,6,5,11,14,9,1,8,0,13,12,4,7}, + {20,30,22,26,19,17,27,25,28,24,29,18,21,23,16,8,5,1,9,15,6,0,7,2,12,10,4,3,13,11,14}, + {25,27,29,22,20,23,19,28,17,30,24,18,21,16,26,11,15,5,10,0,6,9,14,8,2,1,4,12,3,7,13}, + {29,20,24,30,19,21,27,18,17,26,28,16,23,25,22,10,1,13,7,2,9,3,14,8,15,5,12,4,6,0,11}, + {16,23,20,24,28,21,17,27,26,30,18,25,29,22,19,12,15,2,11,7,14,13,1,8,5,9,6,4,10,0,3}, + {20,29,16,24,21,19,25,18,27,26,30,23,28,17,22,6,9,3,5,14,0,7,2,13,10,15,11,1,12,4,8}, + {16,27,30,21,24,26,17,29,19,18,25,28,23,20,22,6,0,14,9,8,12,10,1,13,4,3,5,15,11,7,2}, + {21,23,29,16,26,24,18,22,19,28,25,27,15,20,17,30,12,11,0,6,2,8,5,14,7,4,3,9,1,13,10}, + {22,30,15,23,18,16,25,17,27,26,20,24,29,19,21,28,4,7,6,1,12,11,2,9,0,10,5,8,14,3,13}, + {24,23,17,26,21,18,27,29,20,22,19,25,28,30,16,4,1,0,14,5,3,11,7,13,6,10,12,2,8,15,9}, + {29,22,28,24,30,23,16,20,17,21,27,15,25,18,19,26,7,3,13,2,6,9,1,5,0,12,11,14,8,4,10}, + {20,30,19,26,23,29,21,27,15,24,17,18,22,25,16,28,13,1,0,12,2,14,11,3,9,5,4,6,8,10,7}, + {29,20,17,19,26,24,27,16,22,28,23,25,30,21,18,3,13,11,8,10,7,4,9,14,12,2,1,5,15,6,0}, + {23,19,24,17,20,28,25,21,29,16,30,18,27,26,22,5,7,2,11,1,4,9,14,8,10,6,13,12,15,0,3}, + {22,30,17,15,23,20,18,27,16,28,26,25,29,24,19,21,4,0,2,6,12,11,10,8,1,13,9,5,3,7,14}, + {26,15,17,27,18,24,19,16,28,30,23,20,21,25,29,22,14,0,7,3,10,6,4,1,11,8,13,5,2,12,9}, + {27,18,17,28,23,22,26,24,21,25,20,29,19,16,30,5,11,3,1,8,12,10,7,4,13,9,6,15,14,0,2}, + {18,21,23,28,16,25,22,24,29,17,26,19,27,20,30,2,4,7,10,1,13,8,5,9,15,6,3,12,0,11,14}, + {25,23,16,18,28,27,29,21,24,26,30,17,20,19,15,22,6,1,7,14,4,13,10,3,8,0,2,12,5,11,9}, + {16,24,27,23,21,28,22,19,17,25,30,29,26,20,18,9,1,3,5,10,13,4,15,6,12,7,11,14,0,8,2}, + {27,16,23,29,22,30,26,18,28,21,20,15,24,19,25,17,13,1,5,3,0,8,4,6,11,10,14,12,7,9,2}, + {25,17,16,18,28,23,26,27,20,19,24,15,22,30,29,21,7,4,14,6,13,1,12,5,10,3,9,0,11,8,2}, + {23,27,30,26,17,21,24,19,25,18,28,16,20,22,29,1,0,14,13,12,5,4,7,6,3,9,11,8,2,10,15}, + {30,21,20,26,29,24,28,22,18,25,19,27,17,16,23,2,15,0,14,6,8,1,5,12,11,10,4,9,7,13,3}, + {30,18,17,21,23,19,25,24,28,27,29,20,26,22,15,16,8,4,6,13,10,14,3,2,7,12,1,11,5,0,9}, + {29,23,21,18,15,17,22,26,28,20,16,25,27,30,24,19,1,12,10,6,8,13,14,9,3,2,5,0,11,4,7}, + {20,22,29,24,18,21,28,30,16,25,17,27,19,23,26,10,13,1,7,12,0,5,15,11,2,9,4,6,8,3,14}, + {21,16,15,29,24,20,19,27,25,28,18,23,22,30,26,17,8,12,6,13,3,10,14,2,7,9,5,11,1,0,4}, + {16,24,23,21,17,29,28,25,22,26,18,20,30,19,27,7,13,3,10,1,11,14,8,6,12,15,9,2,0,5,4}, + {26,24,27,20,29,18,22,21,28,23,25,16,19,30,15,17,14,9,13,3,5,7,0,4,2,11,8,12,6,1,10}, + {22,29,24,26,30,28,21,25,17,19,18,20,16,23,15,27,11,1,5,9,0,13,6,8,2,7,4,3,10,12,14}, + {17,22,19,25,28,18,16,29,24,23,21,26,20,30,15,27,11,9,2,7,0,12,4,1,6,8,14,5,3,13,10}, + {27,20,24,29,25,30,21,16,22,28,18,23,19,26,17,14,13,6,1,9,2,10,7,15,11,4,0,8,5,12,3}, + {19,17,20,29,23,21,30,24,16,28,26,18,25,22,27,12,14,1,9,2,15,8,4,6,10,0,7,11,13,3,5}, + {17,26,22,21,29,27,18,20,24,19,28,25,16,30,23,12,7,14,13,8,15,10,6,3,1,9,4,0,5,11,2}, + {16,21,27,26,20,23,25,19,15,18,17,24,22,29,28,30,9,6,3,7,13,4,11,10,5,12,14,8,1,2,0}, + {30,27,21,16,18,28,26,29,15,19,17,25,23,20,22,24,13,10,14,6,11,8,3,0,2,7,9,4,1,5,12}, + {29,27,15,28,20,18,17,21,26,23,19,30,25,16,22,24,5,4,3,12,8,10,9,13,2,11,0,7,14,6,1}, + {16,28,26,25,18,21,19,17,27,23,29,24,22,20,30,3,0,10,14,6,13,8,11,2,7,12,9,4,1,15,5}, + {30,26,15,29,16,27,24,21,25,22,20,18,23,19,28,17,1,3,14,8,10,2,4,13,5,9,6,12,7,0,11}, + {25,15,26,18,24,30,28,16,20,22,19,29,21,27,17,23,0,9,13,5,12,7,3,14,6,8,2,1,4,11,10}, + {29,22,27,30,26,17,24,21,15,28,20,25,23,19,16,18,10,11,8,0,14,7,1,12,6,2,4,3,9,13,5}, + {24,15,25,16,30,29,28,19,21,18,23,27,20,22,17,26,3,14,9,7,11,8,13,10,0,2,6,12,1,5,4}, + {18,22,21,24,30,27,15,19,26,29,23,20,28,16,17,25,9,12,11,2,6,3,7,5,8,13,0,14,4,1,10}, + {15,20,24,28,30,17,22,21,18,29,26,19,25,16,27,23,9,2,5,10,4,0,3,11,6,13,8,7,14,12,1}, + {29,19,28,18,24,26,23,15,30,20,22,17,25,21,27,16,8,7,2,11,9,12,10,5,4,6,0,14,1,13,3}, + {30,18,27,25,28,20,17,22,15,19,24,29,23,26,21,16,7,6,8,4,14,13,12,0,9,1,5,3,11,2,10}, + {17,26,23,20,29,24,21,18,25,16,19,22,28,30,27,3,14,9,4,7,6,1,11,2,12,15,8,13,5,10,0}, + {24,17,23,27,26,20,16,29,28,21,22,18,30,19,25,11,13,12,8,6,2,9,0,4,10,3,15,14,5,7,1}, + {17,18,22,20,19,23,25,27,30,24,16,21,28,26,29,1,5,4,2,15,8,11,3,6,13,7,10,0,12,9,14}, + {29,17,27,15,22,19,28,20,24,26,21,25,18,23,30,16,3,2,6,9,4,8,14,12,0,11,5,13,10,7,1}, + {25,22,21,23,26,24,17,18,28,20,19,27,16,29,15,30,5,3,1,10,4,14,8,13,12,0,9,11,7,6,2}, + {15,24,18,29,16,25,17,28,26,22,21,27,19,23,30,20,9,13,11,8,1,6,12,5,7,0,3,10,2,14,4}, + {16,29,17,23,28,21,24,30,19,26,22,25,20,27,18,11,14,15,12,9,5,2,4,10,1,3,6,13,8,0,7}, + {19,28,22,18,21,16,25,17,27,29,24,30,15,23,26,20,10,7,2,13,0,5,3,1,14,4,9,12,8,11,6}, + {21,17,26,19,23,18,28,20,25,30,29,16,24,15,22,27,14,10,7,6,9,5,13,4,0,2,8,11,3,1,12}, + {30,16,29,19,21,20,25,18,24,27,23,26,15,28,22,17,9,10,3,14,13,0,4,7,6,12,2,1,5,8,11}, + {27,19,15,30,28,24,21,20,18,25,26,17,23,29,22,16,13,7,6,4,14,11,10,1,5,0,2,9,12,3,8}, + {19,28,27,24,21,25,29,20,15,26,30,22,18,17,23,16,12,10,0,8,1,13,6,14,2,5,9,4,3,11,7}, + {25,15,22,24,26,18,28,30,20,21,23,19,17,27,16,29,5,1,14,10,13,9,2,4,12,0,11,8,6,3,7}, + {28,26,23,17,24,15,20,21,19,30,27,29,18,22,25,16,14,10,1,8,6,5,2,13,12,9,7,0,3,11,4}, + {23,28,26,25,16,21,30,20,29,24,27,22,17,19,18,9,14,6,3,0,7,12,11,13,15,4,2,5,1,8,10}, + } + }, + { 32, { + {31,24,22,20,30,23,27,19,25,28,17,29,21,18,26,16,14,3,12,6,7,13,9,8,2,5,4,10,0,11,15,1}, + {24,27,17,26,21,18,31,16,29,23,25,22,30,28,20,19,14,7,1,6,9,8,11,5,3,2,10,15,12,0,13,4}, + {25,31,23,18,29,28,21,30,20,27,19,24,26,17,22,16,0,11,8,14,5,4,9,12,6,1,15,10,7,2,13,3}, + {17,22,31,30,21,19,23,29,25,27,18,24,26,20,28,16,13,1,7,5,12,6,9,14,8,4,3,15,0,2,11,10}, + {20,30,21,29,31,22,16,26,17,18,25,28,19,24,27,23,6,15,9,14,13,8,1,11,5,0,7,3,2,12,10,4}, + {30,26,17,31,21,20,25,22,29,28,23,19,24,18,27,16,5,12,7,15,2,8,3,4,1,0,11,10,14,13,6,9}, + {30,23,29,28,21,27,22,26,24,18,16,19,31,25,17,20,14,13,1,0,10,12,7,11,2,9,6,15,4,3,8,5}, + {22,20,23,28,24,29,18,16,19,27,17,30,26,31,21,25,15,6,5,7,3,9,1,14,2,4,11,10,13,0,12,8}, + {31,23,27,30,21,25,20,22,19,24,26,29,18,28,17,16,15,5,2,10,12,9,14,0,7,13,6,1,8,3,11,4}, + {16,22,31,18,30,23,21,17,24,20,29,26,28,27,19,25,2,7,13,4,1,6,5,9,0,15,11,3,12,10,14,8}, + {21,31,20,26,28,30,25,17,24,23,22,27,19,29,16,18,7,14,3,9,15,6,4,0,10,2,8,13,11,5,1,12}, + {18,17,31,27,25,16,24,21,28,19,23,30,20,22,26,29,14,11,0,3,5,7,2,10,6,4,8,13,12,15,1,9}, + {26,28,22,20,19,29,18,17,27,16,23,31,21,25,24,30,5,4,13,2,6,12,14,7,9,11,0,15,10,1,8,3}, + {18,24,31,27,19,22,17,28,26,29,16,21,23,30,20,25,12,4,1,15,10,2,7,11,14,3,6,0,5,8,13,9}, + {29,25,17,28,23,20,24,21,16,18,31,30,26,19,22,27,0,11,1,13,5,7,14,4,6,3,12,15,9,2,8,10}, + {25,17,26,16,27,18,31,19,30,23,29,20,28,22,24,21,7,15,10,9,12,8,5,11,1,4,2,0,13,3,6,14}, + {16,23,26,21,19,30,18,20,25,27,28,22,31,17,24,29,6,2,10,14,1,3,11,8,5,13,15,4,0,7,9,12}, + {28,20,18,24,31,22,17,30,26,23,19,27,29,21,16,25,8,15,14,12,7,1,5,9,11,0,13,10,6,4,3,2}, + {29,23,16,30,22,18,21,27,24,28,25,17,20,19,31,26,2,11,13,14,3,6,4,1,8,10,15,12,9,0,7,5}, + {24,17,23,29,22,31,27,26,18,16,25,20,28,19,21,30,11,0,13,2,1,14,9,7,10,5,12,8,3,6,4,15}, + {25,18,23,27,17,22,21,24,28,19,16,20,31,30,26,29,4,0,8,5,12,14,10,15,1,9,6,2,7,11,3,13}, + {19,25,31,26,29,23,28,22,20,27,24,30,17,21,18,16,12,14,10,0,7,3,1,5,9,13,15,8,6,11,2,4}, + {26,30,25,17,23,20,27,22,16,21,24,28,19,31,29,18,13,2,15,9,3,11,1,12,7,14,6,8,10,5,0,4}, + {26,31,21,28,25,29,16,24,22,19,23,20,30,18,27,17,14,6,13,5,7,9,4,3,1,10,15,12,8,2,0,11}, + {31,20,28,22,29,16,21,26,17,30,27,18,25,19,24,23,15,8,7,13,4,6,5,14,9,11,2,10,12,1,3,0}, + {30,27,24,23,26,16,21,19,25,17,18,28,22,31,29,20,4,13,3,15,12,7,1,0,8,2,10,9,11,5,14,6}, + {22,25,21,30,18,20,28,19,27,17,23,31,24,26,29,16,14,5,13,10,7,12,9,3,6,1,11,2,8,4,15,0}, + {23,21,16,26,30,27,31,19,28,25,18,24,22,20,29,17,8,13,3,5,1,7,0,11,6,14,10,4,15,2,9,12}, + {24,29,31,22,16,25,28,20,23,26,17,21,27,19,18,30,1,11,7,3,0,9,12,5,4,8,15,2,14,13,10,6}, + {26,30,21,17,28,22,18,25,16,20,19,24,23,29,27,31,1,11,9,4,15,10,12,6,13,0,14,3,2,8,7,5}, + {18,30,29,25,28,27,26,31,23,20,17,19,24,21,16,22,13,1,0,2,6,12,10,3,14,8,7,4,15,9,11,5}, + {22,31,17,24,16,25,27,20,29,21,18,26,23,28,19,30,10,15,9,0,5,3,11,7,1,14,2,12,4,13,6,8}, + {16,17,28,31,23,22,30,27,26,18,25,19,29,20,24,21,14,10,6,15,11,7,9,13,0,5,8,3,2,1,12,4}, + {28,21,30,25,17,29,18,27,16,26,19,24,22,31,20,23,9,6,11,13,4,12,14,3,5,10,2,8,0,7,15,1}, + {30,27,24,22,20,26,18,17,16,23,29,28,19,21,31,25,0,4,10,14,6,5,7,12,2,13,9,1,15,3,11,8}, + {30,19,29,23,16,26,18,17,24,28,22,21,31,25,20,27,4,0,10,3,7,2,14,12,8,1,5,15,11,9,6,13}, + {17,19,30,26,25,31,18,28,24,23,20,29,27,22,16,21,3,10,11,15,12,1,13,6,9,8,7,0,14,4,2,5}, + {23,18,31,29,19,17,30,28,27,22,20,26,25,24,21,16,13,11,5,0,2,15,8,7,9,4,6,14,12,3,1,10}, + {21,23,18,17,20,22,26,30,27,29,31,19,25,24,28,16,12,14,2,6,4,13,11,9,1,15,5,8,3,10,7,0}, + {18,20,25,22,16,17,28,24,23,30,21,29,27,31,19,26,15,6,14,8,10,9,1,5,12,11,7,4,0,13,3,2}, + {18,26,28,22,17,23,25,24,31,16,21,20,19,27,30,29,11,2,1,3,13,10,12,0,6,15,9,7,4,8,5,14}, + {30,20,17,26,23,21,29,25,19,28,27,31,24,22,18,16,7,15,1,14,8,10,5,9,2,11,13,6,4,3,0,12}, + {26,21,19,30,17,24,27,20,22,29,23,16,31,25,28,18,13,8,14,5,15,11,4,6,2,12,3,10,1,9,7,0}, + {26,17,23,21,27,25,19,16,18,24,20,29,22,28,31,30,11,6,14,2,4,9,12,15,8,5,0,10,1,7,13,3}, + {20,16,28,24,29,23,30,19,26,21,25,31,22,27,18,17,0,10,4,8,1,11,15,9,14,13,2,5,12,6,3,7}, + {20,22,31,29,21,26,25,17,27,19,23,28,30,24,18,16,13,6,3,7,14,1,8,11,9,4,10,5,0,2,15,12}, + {25,31,20,26,22,18,28,30,24,21,19,23,27,29,17,16,15,13,1,9,7,0,2,5,11,4,14,10,3,6,12,8}, + {22,29,16,26,24,30,25,21,18,28,27,20,23,19,31,17,6,5,11,14,3,8,7,9,1,4,10,13,2,15,12,0}, + {27,24,30,29,21,20,18,28,31,23,19,16,26,25,17,22,15,14,7,1,8,11,5,12,3,9,6,0,10,2,13,4}, + {26,20,28,29,23,30,25,31,24,21,19,17,27,18,16,22,15,0,2,13,7,1,8,11,5,10,4,9,6,3,12,14}, + {27,21,28,30,20,16,26,17,23,25,22,31,18,24,19,29,14,6,11,1,8,10,2,4,13,12,9,0,3,7,15,5}, + {26,31,22,19,16,29,18,23,30,28,17,24,27,21,20,25,6,13,10,3,5,0,15,11,14,7,2,9,4,1,8,12}, + {30,18,26,19,16,27,20,24,17,21,28,23,22,25,31,29,12,8,14,2,6,11,1,9,4,0,10,7,3,15,13,5}, + {31,17,24,21,23,29,20,26,16,18,22,27,30,28,25,19,0,2,9,1,13,15,5,12,14,3,6,8,11,4,10,7}, + {30,27,16,22,17,28,18,20,23,26,19,29,21,31,25,24,15,4,10,8,3,9,6,14,7,12,0,2,11,5,1,13}, + {18,26,22,29,16,25,30,24,21,19,23,28,17,20,31,27,12,5,14,2,0,4,10,6,1,15,8,3,11,7,9,13}, + {25,16,21,27,26,23,29,28,31,24,19,18,17,30,20,22,9,4,6,11,5,7,12,10,13,1,3,8,15,0,14,2}, + {18,24,23,29,28,27,16,17,25,31,19,26,20,22,21,30,1,7,5,10,12,14,2,13,15,8,3,9,4,11,6,0}, + {29,28,31,17,24,23,16,26,19,18,22,21,20,27,30,25,13,15,5,12,6,0,11,9,1,3,14,2,4,8,10,7}, + {28,19,16,22,29,26,23,30,24,18,31,21,27,25,17,20,10,15,7,5,14,4,9,2,11,3,1,12,0,8,6,13}, + {19,26,16,21,28,18,23,25,24,20,22,29,17,27,31,30,10,12,5,7,9,4,2,6,8,11,15,13,1,0,3,14}, + {17,26,24,16,22,31,18,25,30,29,23,19,27,21,20,28,5,9,6,4,7,11,15,13,1,8,2,14,10,12,3,0}, + {31,18,29,28,22,19,23,20,30,24,26,25,27,21,17,16,0,13,2,12,14,10,7,5,4,8,6,3,11,9,15,1}, + {19,17,21,31,20,27,23,28,25,22,26,24,30,18,16,29,15,14,6,3,5,10,2,13,8,7,0,4,12,9,1,11}, + {20,17,28,26,23,16,19,24,21,27,29,31,30,22,25,18,14,10,8,1,13,0,11,5,15,3,12,4,6,2,7,9}, + {17,26,18,29,28,24,22,27,30,20,25,16,21,31,23,19,6,13,5,8,11,14,2,15,7,0,12,3,10,4,9,1}, + {28,18,27,26,29,21,31,22,25,17,19,24,30,16,23,20,1,14,13,5,15,6,11,4,10,3,2,8,12,0,9,7}, + {27,18,31,26,28,30,16,23,20,19,25,22,29,17,21,24,10,6,4,7,3,14,1,0,12,5,8,11,13,2,9,15}, + {17,26,19,24,23,31,27,21,28,16,22,18,29,20,30,25,1,4,8,13,6,3,2,14,5,9,12,10,7,11,0,15}, + {24,18,26,16,22,19,17,30,29,25,20,31,28,23,27,21,15,13,9,12,1,8,10,6,5,14,7,4,0,2,11,3}, + {28,26,29,18,25,31,24,20,23,27,22,16,21,17,30,19,4,14,0,11,1,7,5,13,6,3,12,2,9,15,8,10}, + {20,18,31,21,29,17,16,23,27,25,19,22,24,26,28,30,15,13,10,12,8,1,9,0,4,14,7,2,6,3,5,11}, + {29,28,26,17,19,25,31,16,30,20,18,24,27,22,21,23,0,13,7,1,11,5,12,15,10,2,4,6,9,3,14,8}, + {30,21,20,31,24,27,22,19,18,29,16,26,17,23,25,28,5,2,6,13,15,8,10,14,0,3,12,4,7,11,1,9}, + {30,24,23,28,26,29,21,27,22,31,25,17,20,19,18,16,6,0,3,8,4,2,11,10,7,13,5,15,9,14,12,1}, + {17,24,26,18,30,20,16,25,27,21,23,22,28,19,31,29,4,6,1,11,15,2,13,9,12,8,5,14,7,3,0,10}, + {20,25,19,29,22,27,18,17,28,23,31,16,26,21,24,30,15,5,9,8,14,3,11,2,10,12,6,1,0,4,13,7}, + {24,31,26,23,28,20,22,27,19,21,25,18,29,16,30,17,15,8,7,14,6,12,10,4,0,13,2,11,5,9,3,1}, + {31,28,27,25,23,20,19,22,24,17,18,29,21,16,26,30,14,7,10,6,4,3,1,13,8,12,5,0,2,15,11,9}, + {16,19,23,17,30,29,24,20,26,22,25,28,27,21,18,31,13,4,14,12,7,2,8,6,9,1,10,11,0,5,15,3}, + {29,24,22,18,20,31,21,28,17,30,27,26,23,16,19,25,15,6,10,1,14,2,0,11,13,7,5,8,4,3,12,9}, + {26,18,31,27,25,28,21,30,20,16,22,24,19,23,29,17,2,0,7,13,6,14,9,8,12,11,5,3,1,4,15,10}, + {16,30,18,31,24,21,27,17,28,26,29,22,20,23,19,25,7,14,6,13,5,12,3,0,9,8,11,15,2,10,1,4}, + {28,25,22,20,29,23,16,31,27,30,17,19,26,18,24,21,12,9,4,2,1,8,5,14,11,7,3,0,15,10,13,6}, + {30,26,29,16,25,22,17,24,19,21,27,31,28,23,20,18,15,14,13,6,1,3,2,5,0,7,10,4,8,12,9,11}, + {19,22,31,29,16,26,20,24,28,27,30,17,21,25,23,18,10,5,3,11,13,15,8,4,6,9,1,12,2,7,14,0}, + {16,17,19,25,28,27,21,29,26,31,20,18,30,22,24,23,14,2,11,7,0,15,13,6,9,8,12,4,1,5,10,3}, + {30,21,26,31,20,16,19,23,29,18,27,22,25,24,28,17,5,13,8,1,4,7,11,15,9,3,2,10,0,14,6,12}, + {16,21,20,23,22,25,29,17,24,19,26,28,30,27,18,31,2,10,6,13,5,15,14,12,1,0,3,9,7,4,8,11}, + {31,25,23,16,18,22,30,29,21,27,24,26,17,19,28,20,2,5,15,1,7,0,6,4,3,14,9,8,10,13,12,11}, + {18,25,22,28,16,20,17,19,27,24,29,26,21,31,23,30,1,12,2,11,0,4,7,5,14,13,9,3,8,15,10,6}, + {31,17,20,19,21,27,30,22,26,23,25,29,16,24,18,28,5,12,9,14,4,2,6,10,3,1,15,11,8,13,7,0}, + {17,23,26,19,24,31,27,29,18,20,22,28,16,25,30,21,4,2,8,3,6,15,10,1,7,11,13,0,9,12,5,14}, + {28,24,22,29,25,19,23,27,31,16,21,20,26,18,17,30,5,10,14,13,2,11,8,1,0,4,6,15,3,7,9,12}, + {29,19,23,22,17,28,16,31,21,27,25,30,20,24,18,26,4,2,5,14,6,11,15,13,0,10,7,1,3,9,12,8}, + {23,18,28,30,29,31,27,20,24,22,21,17,25,19,26,16,0,7,10,12,5,8,6,3,15,2,14,9,11,4,1,13}, + {27,24,22,31,23,26,28,18,30,21,19,29,20,25,17,16,5,3,11,10,2,8,12,0,14,9,13,4,15,7,1,6}, + {22,20,25,31,24,21,29,18,27,17,23,26,28,30,16,19,7,9,13,3,8,10,0,2,4,14,12,1,5,15,11,6}, + {31,19,22,26,30,24,18,28,25,27,23,17,29,21,16,20,4,9,5,2,11,10,14,6,3,1,15,13,8,12,7,0}, + {22,29,31,21,17,23,27,18,26,25,20,28,19,30,24,16,15,0,4,11,7,5,10,13,2,14,3,8,12,6,9,1}, + } + }, + { 33, { + {22,24,18,20,30,26,17,31,27,23,21,25,19,29,32,28,6,14,9,3,8,16,0,4,15,7,2,11,5,10,13,1,12}, + {19,22,26,21,31,28,18,30,29,25,32,17,20,27,23,24,3,15,4,13,16,14,0,6,9,12,11,10,1,5,2,8,7}, + {28,30,17,25,20,24,26,22,32,29,31,18,23,21,27,19,11,9,7,1,4,2,5,12,15,6,0,8,13,10,14,16,3}, + {25,22,30,21,23,28,17,31,27,29,19,18,24,32,26,20,9,1,15,12,7,5,0,16,11,14,6,4,3,8,10,13,2}, + {17,29,31,26,32,16,28,22,20,23,25,19,30,21,24,18,27,3,12,11,2,0,7,1,13,6,15,4,14,10,8,5,9}, + {24,20,28,26,21,31,16,22,29,17,23,27,32,30,25,19,18,12,11,1,4,7,13,14,6,9,0,15,10,2,5,8,3}, + {17,19,30,22,21,27,29,18,26,23,25,32,20,24,31,16,28,11,9,13,8,14,2,12,5,10,0,6,4,15,7,3,1}, + {22,32,25,18,28,17,21,29,20,27,31,16,26,23,30,19,24,3,5,0,9,2,15,1,8,14,11,6,12,7,10,4,13}, + {27,20,22,19,25,24,30,29,21,17,23,26,18,32,31,16,28,9,13,5,12,7,3,10,1,15,0,6,8,4,2,14,11}, + {26,20,28,27,21,25,30,18,16,32,19,24,22,31,23,29,17,7,14,5,9,12,2,10,8,1,6,0,3,15,13,11,4}, + {21,16,23,20,30,22,28,24,26,25,27,19,18,32,17,31,29,0,15,2,14,13,11,10,4,9,3,6,5,7,12,8,1}, + {27,29,24,23,28,17,32,21,20,31,30,19,25,18,16,22,26,12,1,11,10,7,9,0,6,3,15,5,4,13,8,14,2}, + {31,26,21,28,17,20,24,29,23,19,27,25,30,32,22,18,15,5,4,1,16,9,0,11,6,13,10,12,8,3,2,7,14}, + {29,20,22,27,18,17,31,26,28,32,23,19,25,24,21,30,6,16,4,1,3,12,2,9,14,5,7,11,8,10,15,13,0}, + {21,26,25,23,20,16,30,18,28,24,32,31,27,19,22,29,17,1,14,5,7,12,4,11,13,8,15,10,2,9,3,6,0}, + {28,26,22,31,23,30,20,18,27,19,17,29,25,21,32,24,15,8,2,5,0,6,12,16,3,7,11,9,13,1,4,10,14}, + {25,18,24,19,29,26,32,21,31,27,23,20,16,30,22,28,17,15,1,3,0,7,5,10,13,8,14,9,12,4,2,11,6}, + {22,18,27,24,30,19,17,29,32,25,21,31,20,28,23,26,1,8,12,3,11,14,0,4,9,6,13,7,15,2,10,16,5}, + {29,25,17,20,18,27,26,21,30,19,23,28,31,24,32,16,22,14,0,12,3,7,5,8,13,2,6,15,9,11,10,4,1}, + {20,26,24,22,30,21,25,17,28,27,18,31,29,23,19,32,4,15,10,8,14,5,7,13,6,11,9,3,2,16,12,1,0}, + {21,26,16,32,30,19,17,27,25,24,22,28,20,31,18,29,23,6,12,9,7,3,0,13,4,10,2,1,11,8,15,5,14}, + {30,25,18,32,20,22,24,28,27,17,19,31,21,29,26,23,5,12,10,8,11,15,4,7,16,3,13,1,0,6,14,2,9}, + {27,18,29,32,23,28,31,24,20,19,25,21,30,22,26,17,1,8,3,9,11,5,13,6,0,10,14,7,2,16,15,4,12}, + {26,25,18,21,24,29,28,19,17,31,30,20,32,23,22,16,27,6,11,9,2,8,14,0,15,10,13,4,1,3,7,5,12}, + {23,19,29,32,22,20,18,21,31,26,30,28,17,27,25,24,3,13,15,7,11,5,9,1,12,2,8,16,6,0,10,4,14}, + {24,31,22,30,27,25,19,28,32,29,16,18,26,21,20,17,23,12,2,13,7,6,9,14,11,4,8,15,5,10,1,0,3}, + {18,21,25,27,26,16,19,31,30,32,29,24,28,17,23,20,22,10,0,13,2,1,11,14,3,15,4,6,12,8,5,7,9}, + {32,26,28,24,29,22,27,30,17,18,21,19,23,31,25,20,2,1,12,11,9,3,6,4,14,7,0,10,13,15,8,5,16}, + {19,21,23,27,16,25,31,30,28,24,32,22,18,26,17,29,20,14,2,1,11,8,6,4,15,5,13,10,0,9,3,12,7}, + {20,26,17,31,27,23,28,24,29,22,30,25,21,19,18,32,2,7,10,3,16,11,9,14,0,12,15,13,6,8,5,1,4}, + {19,29,17,21,23,32,31,18,26,28,24,27,30,20,22,25,15,13,0,2,12,5,7,10,4,11,3,9,8,1,6,14,16}, + {17,29,18,25,23,26,31,27,20,24,19,32,21,28,30,22,13,16,1,8,11,7,5,15,10,14,12,4,2,6,9,3,0}, + {23,27,29,26,25,32,19,24,18,28,31,17,21,20,30,22,8,13,9,5,15,2,12,0,10,1,7,16,4,3,11,6,14}, + {25,32,24,18,28,27,29,17,30,20,22,19,26,23,21,31,12,10,9,2,0,4,3,15,6,14,7,1,5,16,13,11,8}, + {24,18,26,28,32,31,30,29,20,16,27,19,17,23,22,21,25,7,14,4,8,6,9,15,2,10,12,1,11,3,0,5,13}, + {26,20,30,24,32,28,25,23,17,29,27,31,18,22,21,19,0,8,13,7,9,16,12,1,14,10,6,3,2,11,5,15,4}, + {20,30,25,32,26,29,17,22,18,23,19,28,27,21,31,24,2,10,14,3,12,7,13,8,15,6,4,16,0,9,11,5,1}, + {31,16,24,29,18,23,22,20,17,25,30,26,28,19,32,21,27,13,0,7,15,3,5,8,10,6,2,11,4,1,9,12,14}, + {18,27,19,25,30,24,28,26,17,29,21,20,32,22,31,23,8,11,10,5,9,12,15,3,13,7,6,0,14,1,16,2,4}, + {18,16,26,22,19,23,27,20,24,28,32,29,31,25,21,30,17,14,0,15,9,5,13,1,4,11,6,3,2,7,10,12,8}, + {24,29,20,32,16,30,27,23,17,19,26,31,18,21,28,22,25,12,7,10,9,5,0,3,8,6,14,2,15,11,4,1,13}, + {20,22,24,30,18,25,29,21,26,28,32,31,19,23,17,27,8,16,14,0,7,10,1,15,12,4,13,11,5,6,3,9,2}, + {25,24,31,30,28,22,19,23,20,32,16,18,27,17,26,29,21,4,8,6,1,10,13,2,12,7,5,11,14,15,3,0,9}, + {26,25,19,31,29,21,23,32,30,27,18,22,28,20,17,24,15,16,14,8,6,0,11,13,3,5,10,2,7,4,12,1,9}, + {27,25,32,22,30,17,28,26,20,18,19,24,31,21,29,23,7,0,3,12,8,2,1,14,4,16,9,15,6,5,13,11,10}, + {24,20,22,27,29,31,21,26,32,17,28,30,23,19,18,25,4,13,1,6,0,11,7,12,3,2,10,14,9,16,5,15,8}, + {19,29,24,21,31,27,30,26,25,23,22,32,20,18,28,17,13,0,2,5,7,12,9,4,16,8,3,11,14,1,10,6,15}, + {27,19,21,20,23,16,25,18,28,17,32,30,29,26,22,24,31,11,2,0,14,15,9,5,8,6,12,1,7,4,10,13,3}, + {29,17,20,22,30,18,25,19,21,27,26,24,31,28,23,32,14,16,15,13,2,0,9,1,11,5,10,3,7,4,8,6,12}, + {20,25,30,24,32,19,29,23,22,18,31,21,27,17,28,26,0,15,10,6,3,13,1,8,7,16,9,2,4,12,11,14,5}, + {29,31,22,20,19,17,16,30,25,28,24,32,21,27,26,18,23,0,5,11,4,10,8,3,13,12,14,9,2,7,6,1,15}, + {25,30,28,18,17,24,29,27,31,22,21,26,19,32,20,23,13,1,14,12,8,4,9,16,11,10,15,7,6,2,0,3,5}, + {31,21,30,23,27,26,17,32,29,22,25,18,20,24,28,19,0,11,9,5,12,1,3,2,13,8,7,14,4,10,15,6,16}, + {17,30,19,21,18,22,27,23,28,20,31,29,32,25,24,26,12,1,6,5,3,14,4,7,15,10,9,8,2,11,0,16,13}, + {24,29,32,22,16,19,17,28,26,30,20,18,27,25,31,21,23,0,9,3,8,1,10,4,13,6,5,7,2,15,11,14,12}, + {18,24,19,23,17,22,32,20,25,30,21,31,27,29,26,28,15,10,6,14,1,5,8,3,9,16,11,4,2,12,7,0,13}, + {26,19,21,29,28,30,18,27,31,25,22,24,16,20,23,32,17,11,4,1,3,13,0,9,7,2,5,15,12,10,8,6,14}, + {24,29,27,31,22,18,26,30,23,20,25,32,17,21,19,28,1,9,15,14,12,4,6,13,16,7,0,3,11,5,8,10,2}, + {29,31,23,28,21,25,18,17,22,24,27,20,16,19,26,32,30,15,9,6,4,2,10,7,3,12,0,14,5,11,13,1,8}, + {30,31,25,21,26,32,29,18,28,24,17,20,22,27,16,23,19,13,7,4,10,14,9,8,15,2,0,11,5,12,6,1,3}, + {28,17,31,29,16,26,32,24,19,23,20,22,21,18,27,30,25,5,8,11,13,6,0,15,12,1,3,7,9,4,14,10,2}, + {21,29,16,19,24,23,18,30,26,25,27,22,31,28,20,32,17,15,5,4,3,7,10,2,14,12,6,8,13,9,11,1,0}, + {25,27,17,16,28,26,29,18,21,19,30,32,31,22,20,24,23,14,7,0,6,8,13,10,15,12,1,5,2,4,3,9,11}, + {22,18,16,24,20,28,31,26,23,19,17,32,25,30,29,21,27,12,10,3,5,15,11,7,1,9,13,8,4,14,6,2,0}, + {27,26,31,28,30,17,32,21,24,16,22,29,20,25,23,19,18,14,13,15,12,6,9,10,8,4,0,5,2,7,1,3,11}, + {26,23,25,21,28,32,22,20,31,24,30,19,27,17,29,16,18,3,9,7,12,8,2,10,13,1,5,11,15,14,6,4,0}, + {27,29,18,25,19,28,21,32,30,16,24,22,20,23,31,26,17,0,3,10,14,6,2,4,7,15,11,8,12,5,9,1,13}, + {27,17,20,18,29,24,32,30,28,31,19,16,23,26,25,22,21,5,14,8,13,7,15,9,6,2,4,12,1,0,10,3,11}, + {21,17,25,29,23,31,16,28,27,19,18,32,20,30,24,22,26,4,7,14,0,2,10,9,5,12,6,1,3,15,13,11,8}, + {26,32,17,23,20,18,29,16,25,31,19,21,28,24,22,27,30,13,1,11,4,12,7,10,8,5,9,14,6,2,15,0,3}, + {30,18,20,26,24,21,31,17,23,19,28,32,27,29,22,25,14,16,1,6,12,7,9,8,4,11,3,2,10,13,0,15,5}, + {26,21,32,31,19,22,24,23,18,25,27,20,28,30,17,29,0,6,8,4,14,1,13,9,10,7,11,3,12,2,5,16,15}, + {31,27,20,19,21,23,26,28,24,17,22,30,25,32,18,29,9,15,3,12,10,4,14,5,13,7,16,11,6,8,1,0,2}, + {18,16,23,17,28,21,24,26,30,19,29,25,32,22,27,20,31,10,0,12,15,13,1,3,11,9,7,5,4,2,8,14,6}, + {32,21,16,18,17,19,23,20,30,26,24,27,29,31,28,22,25,8,5,15,2,11,4,1,6,12,9,7,10,13,3,0,14}, + {17,24,25,30,19,32,21,20,29,27,18,22,26,31,28,23,10,15,4,11,0,5,2,13,8,7,9,1,12,14,16,6,3}, + {16,31,22,28,21,18,26,19,30,20,24,29,32,25,23,27,17,4,8,7,14,9,5,2,1,11,0,12,6,10,15,3,13}, + {24,23,18,32,19,21,30,22,20,28,31,25,27,29,26,17,16,2,15,5,8,1,7,10,0,11,14,12,9,13,6,4,3}, + {26,21,30,22,31,19,23,27,16,28,24,29,18,17,20,25,32,15,9,14,11,6,2,10,12,7,3,5,4,0,8,1,13}, + {32,26,31,24,28,25,20,27,23,21,18,17,30,29,22,19,11,3,2,14,9,12,1,5,8,6,0,16,10,7,13,4,15}, + {26,29,27,17,32,28,31,18,25,23,21,30,20,16,24,19,22,3,2,9,12,5,1,8,14,11,15,10,6,0,7,13,4}, + {18,19,29,27,20,28,30,17,21,23,31,24,22,25,32,26,0,7,16,1,3,8,2,4,11,9,14,5,15,12,6,10,13}, + {17,20,24,28,22,18,30,19,23,27,21,26,29,32,31,25,5,0,9,14,4,16,13,15,7,3,2,6,10,1,12,11,8}, + {26,17,31,21,24,29,32,28,22,18,27,23,25,19,30,20,15,10,9,4,14,0,16,5,13,6,12,8,7,11,1,3,2}, + {21,28,26,17,29,24,32,30,20,25,31,27,22,18,23,19,7,16,1,9,2,0,4,3,11,14,6,13,8,10,15,12,5}, + {26,17,27,21,20,16,31,30,22,24,29,28,23,25,18,32,19,14,10,5,7,4,1,0,2,9,3,11,15,8,12,6,13}, + {30,22,32,20,27,31,25,29,24,21,17,19,23,18,26,28,12,9,4,11,13,5,1,10,16,8,14,3,15,7,0,6,2}, + {18,20,29,27,16,31,22,17,32,28,24,19,25,23,30,26,21,15,6,12,1,13,5,11,9,2,14,4,3,10,7,0,8}, + {20,30,19,16,31,28,26,22,32,21,25,17,23,27,29,18,24,11,0,2,9,7,10,12,4,6,15,13,3,14,1,5,8}, + {24,28,19,21,31,20,22,26,17,25,27,29,18,30,23,32,15,13,3,16,10,12,6,4,9,7,1,5,2,14,8,11,0}, + {19,17,25,30,32,31,18,24,29,21,26,28,23,22,27,20,15,13,10,12,14,11,7,16,0,4,3,6,8,1,5,2,9}, + {21,19,26,23,22,24,31,18,32,29,25,16,28,27,17,30,20,5,8,7,14,12,1,3,2,9,6,11,0,10,13,4,15}, + {18,16,20,26,22,19,32,31,24,30,25,17,27,29,21,23,28,9,14,7,5,1,13,15,8,12,3,10,6,11,4,2,0}, + {18,20,26,29,22,27,24,30,21,32,25,23,19,28,31,17,9,13,0,8,10,6,5,1,16,7,2,15,11,14,4,3,12}, + {18,31,17,26,30,28,21,24,22,29,20,19,27,32,25,16,23,6,5,9,1,12,10,13,0,2,4,15,8,11,3,14,7}, + {28,24,23,26,29,25,32,22,17,27,21,19,31,20,18,30,2,12,9,5,16,7,11,1,14,10,3,13,0,8,15,4,6}, + {19,30,24,27,21,32,18,17,26,25,22,28,20,29,16,31,23,9,8,0,14,5,7,12,2,4,1,11,3,10,6,15,13}, + {17,28,19,21,29,23,32,26,20,22,30,25,18,27,24,31,9,14,2,1,12,3,15,0,5,13,8,4,10,16,6,11,7}, + {22,31,28,16,32,30,18,27,29,17,25,21,26,23,19,24,20,9,13,8,2,7,12,0,14,10,15,4,3,6,5,1,11}, + {26,17,19,25,22,18,21,24,23,32,27,20,31,29,28,30,3,15,7,6,13,5,11,16,8,14,2,10,1,12,4,0,9}, + } + }, + { 34, { + {33,29,21,32,18,31,24,30,27,20,28,23,26,22,19,25,17,13,3,16,0,14,2,12,10,4,15,5,11,9,8,1,7,6}, + {27,29,32,20,26,23,21,30,25,19,33,22,31,28,24,18,17,10,4,2,6,8,0,5,7,16,15,13,1,11,14,3,12,9}, + {20,32,25,28,17,26,33,30,21,31,27,18,22,29,19,24,23,1,15,5,16,8,12,6,3,2,0,10,13,11,7,9,4,14}, + {25,18,30,28,32,23,19,22,21,27,29,31,33,20,24,17,26,11,10,3,13,0,7,12,4,6,9,15,14,16,5,2,8,1}, + {27,17,29,32,31,21,20,19,24,22,26,33,30,28,25,18,23,15,7,14,8,5,16,4,9,11,10,1,13,3,12,6,0,2}, + {21,25,24,23,28,18,20,19,26,29,27,33,17,32,22,30,31,8,16,1,5,0,10,6,11,4,7,3,13,15,14,2,12,9}, + {26,29,32,19,24,31,27,25,20,17,22,18,23,21,33,28,30,5,4,3,2,8,16,0,7,14,12,1,13,6,10,15,11,9}, + {30,29,28,20,24,21,25,32,31,19,17,33,27,26,23,18,22,6,2,9,15,0,4,8,5,10,1,11,13,16,12,3,7,14}, + {29,31,25,21,32,23,19,22,28,18,24,33,20,27,26,30,17,6,1,3,8,2,13,11,10,4,9,16,14,0,7,5,15,12}, + {17,33,27,31,26,32,23,28,25,19,22,29,20,24,30,21,18,16,8,7,3,6,2,15,1,13,12,5,0,4,14,10,9,11}, + {26,23,29,32,22,21,18,24,28,17,30,20,25,31,19,27,33,7,0,5,4,14,13,11,2,9,3,6,16,12,8,10,1,15}, + {22,24,18,33,27,29,20,30,32,26,25,17,23,21,19,28,31,12,11,7,10,16,6,13,5,3,2,15,14,1,4,0,9,8}, + {17,22,20,26,19,25,30,33,31,24,28,21,27,32,23,18,29,15,11,7,2,14,9,13,12,5,0,10,3,8,6,16,1,4}, + {22,20,31,29,18,26,23,30,27,32,25,19,24,21,28,33,17,16,0,13,1,11,9,12,2,7,10,14,4,8,15,3,6,5}, + {29,27,23,33,31,22,17,18,20,30,32,28,26,25,24,21,19,13,9,3,11,0,12,15,2,7,10,14,16,8,5,1,6,4}, + {21,18,26,19,29,31,23,30,25,32,27,17,22,24,28,33,20,13,1,3,5,2,4,12,16,9,6,0,14,8,10,15,7,11}, + {17,32,21,19,24,23,27,26,29,20,30,33,31,28,22,25,18,12,9,7,10,2,6,3,15,0,8,4,16,13,11,1,5,14}, + {20,23,32,22,31,28,27,17,24,33,18,25,29,19,30,21,26,16,6,10,3,1,12,5,9,7,2,8,11,4,0,15,14,13}, + {30,23,22,31,29,19,18,25,33,32,17,26,20,24,28,27,21,16,9,5,7,11,10,15,3,12,2,6,0,4,14,13,1,8}, + {31,20,30,33,23,28,25,22,27,24,29,21,19,32,18,26,17,14,12,1,4,6,9,0,5,2,10,3,13,8,7,16,11,15}, + {18,31,24,23,32,17,27,25,33,29,28,19,21,20,22,26,30,14,8,6,4,12,5,16,2,7,15,11,10,9,3,1,13,0}, + {28,33,27,22,17,29,26,20,32,23,19,21,30,18,31,24,25,5,1,10,8,15,13,11,14,7,6,16,4,3,12,9,0,2}, + {32,30,28,33,23,29,26,25,19,31,24,27,21,20,17,22,18,12,7,11,2,16,10,4,13,0,6,8,15,5,1,9,3,14}, + {24,22,25,17,31,33,28,20,30,32,18,21,29,27,23,26,19,16,1,5,9,4,3,6,8,7,10,2,12,0,11,15,14,13}, + {20,32,24,27,31,29,17,21,28,30,19,22,25,33,26,23,18,10,8,15,3,1,5,4,16,7,13,11,0,9,12,14,2,6}, + {28,22,32,30,27,25,33,21,23,29,26,31,20,17,18,24,19,16,9,8,5,2,12,15,14,10,13,0,3,7,1,11,6,4}, + {22,30,26,20,28,23,32,27,24,19,21,18,29,25,33,17,31,0,4,13,8,6,9,14,1,16,2,7,15,10,12,11,5,3}, + {20,32,17,26,28,30,19,25,18,29,21,23,24,27,31,33,22,13,11,15,2,1,16,3,6,5,12,10,7,9,14,0,4,8}, + {26,21,17,28,33,31,29,23,20,30,27,32,24,19,18,25,22,9,11,6,8,14,2,16,12,1,4,10,5,0,3,15,7,13}, + {27,25,30,22,32,18,31,23,19,26,20,29,33,28,24,21,17,16,7,10,1,0,11,5,13,6,14,2,4,12,9,15,3,8}, + {28,25,30,20,26,23,22,32,19,33,21,31,27,18,29,24,17,5,1,0,14,16,6,2,15,11,4,12,7,13,9,8,3,10}, + {30,22,29,32,23,26,21,24,18,20,28,19,27,25,17,31,33,16,14,11,15,3,13,2,4,12,8,10,5,9,0,7,6,1}, + {22,26,33,27,24,19,30,28,31,29,32,18,23,20,25,17,21,2,4,0,5,9,6,3,8,11,1,14,7,15,13,12,10,16}, + {23,17,26,25,21,28,33,19,29,31,20,32,30,27,24,18,22,7,5,4,16,9,15,13,0,2,11,6,12,10,1,3,8,14}, + {31,21,25,29,28,19,24,18,26,30,22,32,17,23,33,20,27,16,1,3,13,15,14,8,12,0,10,6,9,11,4,2,7,5}, + {29,33,27,30,23,17,25,24,19,32,31,20,22,26,28,21,18,16,3,15,10,6,14,1,0,11,4,9,12,7,2,8,13,5}, + {33,17,23,31,18,26,30,32,25,21,20,27,24,22,28,19,29,4,7,5,14,2,0,15,12,8,6,11,13,3,10,1,16,9}, + {30,27,26,31,23,28,33,24,17,20,32,21,19,25,29,18,22,10,7,5,11,9,15,0,2,8,13,3,12,14,4,1,16,6}, + {30,22,29,27,20,25,23,18,17,28,19,31,26,21,33,32,24,11,2,12,9,16,5,15,6,4,13,7,1,0,3,10,8,14}, + {22,30,19,27,33,26,20,29,21,24,18,25,32,28,23,31,17,0,3,8,11,9,15,2,14,5,7,12,16,13,4,10,6,1}, + {24,27,18,28,25,33,19,31,30,22,21,23,26,29,20,32,17,2,8,6,1,14,3,5,16,7,11,9,4,13,0,12,10,15}, + {21,22,28,32,24,23,19,18,30,26,20,29,17,27,33,31,25,12,7,0,16,3,14,10,4,1,9,6,2,11,8,15,13,5}, + {29,23,22,28,17,21,24,32,30,19,25,20,33,18,31,27,26,11,5,1,8,2,10,13,4,0,7,9,15,3,6,14,12,16}, + {20,27,29,33,30,23,28,19,26,32,21,25,24,31,22,18,17,9,13,3,16,5,12,1,6,10,8,15,11,7,14,2,0,4}, + {20,22,26,32,21,29,19,25,33,23,30,28,18,31,24,17,27,9,11,5,1,10,3,8,2,16,14,7,4,6,0,13,15,12}, + {26,19,17,33,21,32,29,27,20,23,30,25,22,28,31,18,24,2,12,11,8,5,9,16,14,10,7,15,3,6,1,4,0,13}, + {33,20,18,26,19,22,32,23,31,30,27,25,17,29,28,24,21,15,7,10,14,0,13,5,9,4,6,3,2,12,11,1,8,16}, + {24,21,19,29,23,27,20,18,30,26,31,22,28,17,32,25,33,16,9,12,14,8,5,7,10,1,15,13,3,0,2,4,6,11}, + {31,20,32,24,19,27,33,21,30,18,22,17,28,26,23,25,29,16,3,9,8,15,1,5,0,4,14,7,2,6,13,10,12,11}, + {20,27,24,18,29,25,33,22,32,17,21,30,23,26,28,19,31,8,1,14,0,11,3,5,9,4,2,10,13,6,15,7,12,16}, + {25,27,20,23,28,21,24,19,33,22,18,31,30,29,26,32,17,9,13,7,15,2,8,14,0,11,4,3,12,5,16,1,10,6}, + {18,28,30,33,29,21,23,32,25,31,27,19,24,26,17,22,20,8,0,2,7,13,15,11,12,14,5,10,1,3,6,9,16,4}, + {27,26,29,23,31,30,28,25,19,18,33,17,20,21,24,22,32,5,7,15,12,9,2,8,1,0,14,13,6,11,3,10,16,4}, + {20,28,21,26,25,22,19,33,32,27,31,24,23,29,18,17,30,13,1,10,8,12,4,2,11,9,6,15,7,3,0,5,16,14}, + {20,18,22,28,23,30,29,25,33,26,19,27,24,32,21,17,31,7,0,16,11,6,8,2,14,12,3,10,1,5,15,13,9,4}, + {26,22,32,25,23,28,17,20,30,27,31,24,29,18,33,21,19,0,7,4,13,12,10,11,3,2,9,5,16,14,6,15,1,8}, + {19,33,26,21,31,18,20,27,24,32,23,17,29,25,28,30,22,4,3,13,9,7,11,16,15,5,2,14,10,1,8,0,6,12}, + {27,29,19,25,30,28,33,18,23,22,31,17,20,24,32,21,26,7,6,13,8,5,0,12,9,16,11,4,3,14,1,10,2,15}, + {21,25,22,19,26,30,32,29,18,24,23,27,20,31,33,28,17,16,0,3,14,1,15,9,7,5,13,8,4,12,11,10,6,2}, + {29,32,20,19,18,26,17,22,21,24,27,33,31,28,30,23,25,8,15,3,13,7,16,0,14,6,9,4,2,12,5,10,1,11}, + {28,30,32,19,27,33,20,29,31,25,23,17,21,18,22,24,26,4,7,10,14,8,1,6,13,5,11,2,15,9,12,0,16,3}, + {21,24,28,23,26,33,22,18,17,19,30,32,29,27,25,31,20,7,5,4,0,10,2,15,6,14,9,1,3,11,13,8,12,16}, + {32,20,19,27,23,33,21,26,28,24,31,25,22,18,29,17,30,9,3,13,6,0,12,16,14,11,2,7,5,1,8,10,15,4}, + {27,18,32,19,22,29,30,17,20,31,33,25,24,26,21,23,28,12,1,3,11,7,15,0,14,5,8,4,16,13,6,9,2,10}, + {28,32,29,19,26,22,24,30,21,18,31,27,33,25,23,20,17,0,14,13,2,7,3,9,6,11,1,8,16,4,15,10,12,5}, + {32,26,30,17,28,18,33,24,20,25,27,22,29,23,19,21,31,8,14,9,2,11,6,0,7,10,4,12,5,16,3,15,13,1}, + {30,23,32,19,18,27,26,22,24,29,17,33,31,25,28,20,21,0,11,8,2,16,1,4,13,5,15,12,10,9,7,14,3,6}, + {22,27,26,24,21,17,33,30,19,31,28,25,20,32,18,23,29,11,1,3,0,7,16,6,13,8,12,14,10,5,4,15,2,9}, + {28,27,29,31,33,25,21,30,24,32,18,23,19,22,20,26,17,10,15,14,1,9,5,4,11,16,3,0,7,12,6,8,2,13}, + {19,27,25,28,31,23,20,30,21,18,17,26,24,32,22,33,29,8,4,12,15,3,1,14,7,13,9,16,0,2,6,5,11,10}, + {28,30,33,25,22,24,21,18,20,23,32,31,17,27,26,19,29,6,13,1,4,14,12,9,8,11,5,16,0,2,15,10,3,7}, + {22,18,31,23,21,26,20,27,30,29,19,28,33,24,32,25,17,15,12,16,0,8,14,2,10,13,6,11,3,1,9,7,4,5}, + {31,19,28,17,23,27,29,21,33,26,25,32,20,24,22,30,18,2,14,7,10,9,6,4,16,11,0,12,8,5,1,3,13,15}, + {32,31,30,23,17,26,22,27,29,20,25,18,21,33,24,28,19,16,0,9,8,6,3,12,15,11,14,2,13,4,10,7,5,1}, + {32,18,30,24,21,29,28,17,27,31,23,20,22,33,26,25,19,1,15,4,13,9,14,12,3,10,7,8,6,16,2,5,0,11}, + {17,23,30,25,24,20,32,31,33,27,22,19,29,26,21,28,18,1,8,7,3,5,12,4,6,16,0,13,11,14,2,10,9,15}, + {21,27,17,29,33,22,28,23,25,30,19,32,20,31,26,18,24,4,11,1,12,14,13,6,2,8,0,10,15,7,9,5,3,16}, + {17,28,26,19,32,29,18,23,25,22,30,27,31,33,21,24,20,16,15,8,7,4,6,10,2,12,1,13,0,9,3,11,14,5}, + {24,23,27,20,18,33,22,26,29,25,28,17,30,19,21,32,31,16,7,0,9,1,8,11,2,6,5,10,4,14,3,15,13,12}, + {33,31,20,18,29,24,19,32,26,25,30,22,21,17,23,28,27,0,4,16,2,14,8,7,12,1,3,6,15,10,5,13,9,11}, + {26,31,21,18,24,23,22,29,33,19,25,30,28,27,20,32,17,10,12,0,5,9,4,6,3,2,1,15,13,11,14,8,16,7}, + {32,31,27,25,28,21,17,33,30,19,24,18,29,26,23,22,20,16,12,15,11,0,3,14,7,1,6,5,13,8,4,10,9,2}, + {21,24,28,19,17,27,30,23,32,18,22,26,33,20,31,25,29,16,3,7,13,6,10,0,5,15,8,14,9,12,11,2,4,1}, + {20,32,30,18,26,25,28,27,22,31,29,21,19,33,24,23,17,3,2,15,0,7,11,5,9,12,4,8,10,13,1,16,14,6}, + {28,18,31,21,25,19,23,27,33,29,22,24,26,32,20,30,17,6,4,8,13,9,1,7,3,15,0,12,2,11,16,5,10,14}, + {17,23,30,26,33,24,29,22,32,21,25,20,19,27,31,18,28,16,14,3,13,11,9,7,2,15,8,0,10,6,5,4,12,1}, + {25,22,26,20,19,17,31,29,28,18,32,30,33,24,21,23,27,10,13,12,0,5,9,2,4,14,7,3,8,15,16,1,11,6}, + {23,30,28,19,25,27,17,21,33,31,22,24,20,18,32,29,26,6,0,15,2,12,1,5,7,10,9,8,16,11,3,14,4,13}, + {29,27,22,18,21,23,25,20,28,24,32,31,30,19,26,33,17,12,8,11,15,2,14,5,16,1,4,3,13,7,6,10,0,9}, + {28,20,19,17,29,33,24,22,21,31,27,32,30,18,25,23,26,0,10,13,6,8,16,14,7,5,4,3,9,15,12,1,11,2}, + {28,18,20,27,33,26,21,32,31,29,19,22,24,23,25,30,17,14,10,9,12,3,11,13,2,4,8,6,0,7,5,1,16,15}, + {29,25,31,28,23,17,19,32,21,27,22,33,20,30,24,26,18,12,2,6,0,9,11,13,5,3,15,4,7,16,8,1,10,14}, + {18,25,23,19,33,26,21,17,27,30,20,29,22,32,31,24,28,3,12,10,4,7,0,9,1,6,11,13,2,15,5,8,14,16}, + {18,22,26,28,31,20,19,27,21,32,29,24,33,25,17,23,30,14,4,9,15,10,16,2,6,11,8,5,1,3,0,13,12,7}, + {23,28,19,18,32,31,25,33,30,20,27,26,21,24,22,17,29,5,15,13,4,8,2,11,0,9,16,14,7,12,6,3,10,1}, + {26,33,31,18,19,22,25,21,24,32,29,20,23,28,27,30,17,3,9,12,11,7,5,4,13,6,1,14,16,2,15,8,10,0}, + {30,33,24,21,19,20,27,31,29,32,17,25,28,26,23,18,22,15,7,5,0,11,10,1,12,3,13,6,8,4,16,2,9,14}, + {26,18,32,20,25,27,31,30,21,29,28,23,22,19,24,33,17,16,4,13,11,7,5,1,3,15,0,8,14,9,12,6,10,2}, + {27,26,25,33,19,24,32,23,30,18,21,31,20,17,28,22,29,5,7,1,0,4,15,8,6,14,10,2,13,9,12,3,11,16}, + {25,20,32,18,21,30,22,29,28,31,27,24,26,19,23,33,17,3,1,7,5,0,11,8,14,2,4,10,13,16,6,9,15,12}, + } + }, + { 35, { + {34,30,29,33,18,23,21,28,32,24,31,27,17,20,22,26,25,19,2,7,9,15,14,10,5,1,13,3,0,11,4,12,6,16,8}, + {28,20,31,23,27,25,26,17,21,33,32,34,19,29,18,24,30,22,2,1,4,13,7,3,12,9,6,15,0,8,14,16,10,5,11}, + {21,31,17,28,23,27,32,29,19,22,25,20,26,30,34,18,24,33,8,7,4,0,9,11,16,2,12,10,3,15,13,1,6,14,5}, + {23,28,21,20,29,34,27,26,22,33,31,18,30,24,32,19,25,17,8,1,3,2,14,6,0,11,7,13,15,4,16,10,12,9,5}, + {19,34,25,32,28,20,24,33,21,29,22,23,30,26,31,18,27,7,15,12,17,8,10,4,6,2,13,16,11,0,9,14,3,5,1}, + {17,26,21,25,30,34,27,23,19,22,18,20,29,24,32,28,31,33,14,1,7,4,9,3,12,11,16,0,10,5,15,8,6,2,13}, + {28,24,23,22,34,27,19,25,29,21,32,30,26,31,33,20,18,17,5,15,9,11,3,2,1,10,0,16,12,8,6,13,7,14,4}, + {24,27,26,33,22,25,21,17,28,32,20,34,23,30,19,18,31,29,9,11,3,6,16,13,4,15,14,0,2,12,8,5,1,10,7}, + {34,24,30,20,17,31,28,26,21,19,27,29,25,23,32,22,33,18,16,1,4,11,3,5,0,15,13,9,14,12,2,6,10,8,7}, + {21,18,29,19,33,28,20,24,32,30,27,34,26,23,25,17,22,31,15,11,0,7,14,5,13,8,9,16,10,6,2,4,3,1,12}, + {28,30,21,27,23,34,18,31,33,29,22,26,25,32,20,19,17,24,14,11,13,16,1,3,12,10,0,8,7,2,15,5,9,6,4}, + {21,20,27,31,33,23,25,22,32,34,29,28,24,19,18,30,26,2,17,12,1,0,14,13,5,8,16,11,9,7,15,10,6,4,3}, + {25,29,33,18,26,28,19,22,21,24,20,32,27,23,17,31,34,30,14,8,12,2,4,1,0,16,10,9,6,15,13,11,3,5,7}, + {21,32,22,17,33,27,23,29,24,18,31,28,26,20,25,19,34,30,16,0,5,13,12,14,3,9,2,10,6,8,1,15,7,4,11}, + {18,20,26,17,33,23,19,29,21,25,28,27,22,24,31,32,34,30,13,16,1,11,5,2,10,3,9,15,14,4,8,7,0,6,12}, + {32,18,17,34,27,30,22,25,20,28,19,24,26,31,23,33,29,21,4,16,7,0,10,3,9,13,6,12,1,15,8,2,14,5,11}, + {19,34,17,23,27,32,20,24,28,21,33,31,30,25,29,26,22,18,7,12,3,16,13,15,10,5,9,8,2,4,11,0,6,14,1}, + {19,18,28,31,34,26,22,20,30,24,21,25,23,29,27,33,32,5,17,16,1,0,4,2,13,7,3,6,11,14,9,8,15,12,10}, + {25,20,24,31,23,33,26,28,21,19,27,22,29,18,34,30,32,16,4,17,1,14,3,2,13,0,6,8,7,11,15,12,10,5,9}, + {26,22,24,30,18,19,33,21,32,27,25,23,29,20,31,28,34,9,2,14,17,13,1,16,0,4,11,7,10,12,3,5,8,6,15}, + {20,32,21,29,34,27,25,23,31,28,33,19,24,30,18,26,22,14,12,7,1,17,2,5,15,0,10,13,6,9,16,8,4,11,3}, + {20,34,30,18,17,25,29,19,31,33,28,24,32,27,21,23,22,26,16,12,10,5,14,8,2,13,9,15,1,4,0,7,6,3,11}, + {24,32,20,23,21,19,27,30,18,29,26,22,34,25,28,33,31,7,17,6,9,4,16,15,14,8,10,0,11,3,5,1,13,2,12}, + {25,27,17,31,20,28,19,26,23,21,18,33,29,22,32,30,24,34,4,6,0,14,12,1,15,7,10,13,16,9,2,8,3,11,5}, + {19,23,31,28,32,18,22,26,24,20,30,33,21,27,34,29,25,16,15,14,11,8,3,7,12,10,5,1,0,13,9,17,4,6,2}, + {27,31,19,34,29,28,23,30,20,33,24,18,26,21,32,25,22,13,17,8,16,3,9,12,11,10,14,1,5,7,2,0,6,4,15}, + {19,18,27,29,21,28,22,31,23,34,17,30,32,24,33,25,20,26,3,16,13,1,11,0,8,15,4,6,5,7,2,10,12,14,9}, + {20,27,23,24,31,19,29,34,22,33,21,28,30,32,26,18,25,6,17,4,11,13,9,5,3,16,8,1,7,2,10,15,12,14,0}, + {26,23,29,34,28,25,20,24,27,21,18,33,22,19,30,32,31,14,9,6,16,3,8,7,2,10,17,12,4,11,15,13,1,5,0}, + {26,30,29,33,31,27,18,20,23,25,28,34,32,22,21,24,17,19,8,9,1,13,3,12,4,16,10,2,6,5,14,7,15,0,11}, + {33,23,21,20,31,27,24,28,18,19,26,25,29,17,30,32,34,22,13,11,0,7,16,12,9,6,5,14,2,8,10,15,1,4,3}, + {22,19,34,33,32,20,18,26,31,25,24,30,21,29,28,23,27,12,17,3,11,9,16,15,14,0,4,1,8,6,10,5,7,13,2}, + {17,32,28,30,18,20,19,26,29,34,27,23,21,31,24,33,25,22,10,13,3,8,6,16,2,9,14,1,15,5,11,7,4,12,0}, + {19,30,22,32,21,18,29,27,33,31,28,23,34,25,24,20,26,16,14,4,12,5,13,3,9,2,8,1,10,17,15,0,7,11,6}, + {33,31,30,24,29,27,21,23,20,26,19,32,22,25,34,28,18,14,17,9,12,5,1,0,8,10,13,3,15,7,6,4,16,2,11}, + {32,19,17,26,28,34,30,23,31,22,29,27,18,24,21,33,25,20,15,4,9,13,16,11,3,0,8,5,1,10,6,14,12,7,2}, + {31,20,24,21,34,33,29,26,28,30,19,18,23,27,22,32,17,25,11,5,4,10,1,0,3,16,7,14,13,8,15,2,6,12,9}, + {17,34,19,32,28,31,26,30,27,33,20,23,29,21,25,22,24,18,5,3,0,11,9,13,10,1,8,4,14,16,7,6,2,12,15}, + {29,33,20,31,34,24,28,18,25,22,19,32,26,27,23,21,30,0,16,4,2,10,8,14,5,13,12,9,6,17,1,3,11,7,15}, + {19,28,26,25,33,31,30,24,29,23,34,20,22,27,18,32,21,15,1,12,6,9,2,11,17,8,10,5,3,14,13,4,16,7,0}, + {27,23,30,21,31,33,25,20,22,32,17,26,24,34,18,29,28,19,13,12,7,9,14,5,11,15,10,2,1,8,3,0,16,4,6}, + {18,21,27,33,24,19,28,30,29,22,26,34,32,31,23,20,25,5,8,12,6,14,4,10,2,11,1,17,13,16,7,9,0,15,3}, + {24,32,31,30,27,22,18,28,20,33,23,26,19,25,29,21,34,8,11,7,4,3,15,13,10,9,1,16,0,12,17,5,14,6,2}, + {33,29,27,23,28,20,24,34,25,31,30,21,22,19,18,26,32,3,17,12,2,10,1,6,11,8,4,9,13,15,0,7,16,14,5}, + {33,32,23,31,18,17,28,21,24,19,22,20,29,26,25,30,27,34,13,8,11,9,12,15,1,4,14,3,6,10,2,0,5,16,7}, + {19,23,30,29,22,25,21,24,26,20,31,32,34,27,33,28,18,6,17,8,14,10,4,16,7,15,12,2,13,3,9,0,11,5,1}, + {27,33,21,32,34,24,22,29,23,30,19,26,25,20,28,31,18,2,10,5,8,0,11,6,3,1,13,15,9,17,4,12,16,7,14}, + {34,27,22,29,24,31,20,28,26,18,32,17,25,23,33,21,19,30,6,5,13,3,15,1,16,0,12,8,4,11,10,2,14,9,7}, + {30,18,29,20,26,21,25,19,27,22,33,24,28,23,34,31,17,32,1,13,4,2,8,10,11,7,0,15,12,3,6,9,14,16,5}, + {30,34,23,17,29,25,21,18,22,24,27,26,31,19,28,32,20,33,13,12,16,0,8,1,6,3,11,5,10,4,9,7,14,15,2}, + {19,30,23,18,25,27,29,26,20,24,31,22,33,21,17,32,34,28,16,0,2,6,3,1,10,8,7,4,9,15,12,14,5,13,11}, + {29,34,27,31,21,26,23,22,24,30,28,19,25,32,20,18,33,6,12,3,10,1,14,5,15,0,9,2,8,7,4,16,11,13,17}, + {30,26,18,32,24,21,20,29,23,25,27,19,31,34,28,22,33,8,11,5,17,15,12,1,6,9,7,14,3,0,4,16,10,2,13}, + {22,18,23,31,21,17,19,26,30,27,34,33,29,24,28,32,20,25,16,11,10,4,12,7,15,5,1,6,2,0,14,13,8,3,9}, + {31,23,21,34,19,26,20,22,30,25,24,33,32,28,18,29,27,12,3,0,2,6,8,1,15,10,5,14,13,16,7,17,4,11,9}, + {21,31,28,20,24,30,19,23,32,22,27,26,18,34,25,29,17,33,5,9,2,11,15,1,10,0,4,6,16,7,14,13,12,3,8}, + {28,19,23,22,34,32,21,26,18,27,31,17,25,24,29,20,33,30,0,11,2,6,3,13,15,12,7,9,1,4,8,10,5,16,14}, + {22,20,23,30,32,19,29,25,27,26,34,33,24,21,17,31,28,18,16,5,10,3,8,0,6,9,7,1,15,4,11,14,2,13,12}, + {31,19,25,18,30,21,29,27,34,32,26,20,22,24,28,33,23,6,17,15,0,4,14,7,2,10,13,12,16,5,8,11,9,1,3}, + {18,19,33,26,30,28,25,24,31,27,34,20,29,22,32,21,23,16,14,11,7,2,15,12,3,5,17,10,1,0,8,6,9,13,4}, + {27,25,28,33,22,18,17,30,24,21,23,32,34,20,29,31,19,26,4,0,13,1,3,8,10,9,6,7,15,14,16,5,11,2,12}, + {33,28,31,26,18,20,23,30,25,27,29,17,21,24,19,32,34,22,13,5,10,7,15,1,4,8,12,14,16,0,3,11,2,9,6}, + {26,30,29,23,34,21,19,18,22,32,27,31,25,33,28,17,24,20,2,11,16,9,15,0,6,12,8,14,1,3,5,7,10,13,4}, + {21,30,27,17,19,25,22,24,32,28,34,33,23,26,20,31,29,18,5,7,13,6,16,14,2,3,12,4,10,8,0,9,1,11,15}, + {20,26,22,19,30,24,33,32,28,27,34,25,29,31,18,23,21,15,6,1,3,11,0,14,8,10,5,12,9,17,7,13,16,2,4}, + {28,32,21,18,27,22,25,30,33,31,23,20,19,24,26,29,34,16,3,2,1,12,14,17,5,11,6,4,10,7,13,8,0,9,15}, + {29,33,31,18,22,32,24,27,21,20,26,34,28,25,19,30,17,23,4,1,11,6,9,0,16,14,3,15,2,7,13,12,8,10,5}, + {26,20,29,19,34,24,18,23,22,30,28,31,25,32,21,33,27,16,6,3,1,10,17,7,15,12,4,11,5,0,9,8,2,14,13}, + {21,26,27,22,32,30,25,34,20,18,24,19,31,33,28,23,17,29,3,11,8,5,10,2,6,12,14,13,1,0,16,7,9,4,15}, + {20,24,29,22,21,17,33,32,18,23,28,27,34,30,26,25,31,19,10,7,15,1,16,12,6,14,9,0,2,11,13,4,3,8,5}, + {29,18,22,26,20,24,23,34,21,32,27,33,30,25,28,19,31,4,9,7,11,0,15,3,2,17,14,1,16,5,12,10,13,8,6}, + {25,33,29,27,21,34,20,32,19,23,26,28,18,31,30,24,22,14,12,1,17,10,16,9,7,5,4,8,11,2,6,13,15,3,0}, + {22,19,20,26,17,31,29,25,28,24,27,23,33,21,34,30,32,18,10,4,8,13,0,12,15,7,5,16,6,14,9,11,1,3,2}, + {23,27,30,25,33,19,21,32,22,28,17,26,31,18,29,20,34,24,16,5,13,0,12,10,6,1,11,3,7,15,9,8,2,4,14}, + {25,30,18,23,26,29,28,20,19,31,21,27,34,22,32,24,33,12,16,3,1,14,11,0,15,4,10,2,8,6,17,7,9,13,5}, + {24,18,30,23,26,20,22,19,28,27,29,21,32,25,33,31,34,10,9,15,14,4,6,2,5,3,16,7,12,1,0,8,17,11,13}, + {29,31,28,24,25,32,20,34,18,21,23,27,30,33,19,22,26,0,17,14,6,12,2,4,1,7,15,11,3,8,13,16,9,5,10}, + {24,34,31,23,19,27,17,26,20,30,25,21,33,29,32,28,22,18,0,8,5,4,10,1,14,2,7,16,6,3,15,9,13,12,11}, + {34,28,27,30,33,19,31,26,23,21,25,24,32,29,22,18,20,17,9,13,8,12,1,3,11,6,15,10,14,5,0,4,7,2,16}, + {30,26,21,32,28,33,31,20,27,25,23,29,18,17,24,34,22,19,8,7,10,4,1,6,13,0,2,9,12,11,16,15,3,14,5}, + {18,26,24,29,21,20,30,34,28,22,32,31,33,27,23,19,25,15,5,9,8,14,16,4,11,2,0,6,12,7,3,1,17,10,13}, + {19,33,20,25,32,31,28,27,22,17,24,23,18,34,26,29,21,30,13,15,10,16,14,2,1,5,11,4,7,6,8,3,0,9,12}, + {25,33,22,26,31,29,32,28,34,27,30,17,21,20,24,19,23,18,11,9,1,7,15,10,13,3,0,4,14,12,5,16,6,8,2}, + {18,23,21,19,22,26,34,28,31,30,29,32,27,25,33,24,20,15,13,5,14,8,3,17,1,16,4,10,6,2,0,9,7,12,11}, + {34,29,23,31,24,19,21,26,28,20,32,18,25,22,33,30,27,16,0,2,4,15,9,6,13,1,14,10,12,11,3,5,8,7,17}, + {30,22,27,34,24,26,20,25,18,33,31,29,19,32,21,28,23,16,4,9,12,10,13,8,15,7,2,5,3,1,6,14,11,17,0}, + {25,28,17,29,21,33,23,18,34,31,26,30,20,27,32,22,24,19,1,12,9,15,7,3,0,6,11,13,5,4,14,16,8,10,2}, + {26,29,21,19,31,27,33,20,24,18,34,30,22,25,32,28,23,16,17,3,2,10,4,7,11,5,14,1,13,8,6,9,0,12,15}, + {30,21,33,32,28,19,17,23,34,24,20,27,25,29,31,26,22,18,0,12,9,14,16,7,5,1,3,2,6,15,10,4,11,13,8}, + {17,33,30,26,28,18,20,27,22,32,34,23,19,24,29,25,21,31,5,12,10,9,1,7,6,2,14,15,13,0,11,3,16,8,4}, + {33,21,20,29,17,19,27,22,34,31,30,23,28,24,26,25,32,18,14,13,9,8,12,0,3,7,11,10,16,5,15,4,2,6,1}, + {18,31,33,17,28,30,24,23,34,20,32,26,22,21,27,25,29,19,1,5,7,14,4,12,6,15,11,0,16,3,10,2,9,13,8}, + {20,28,19,30,34,25,27,32,31,24,26,29,21,33,22,17,18,23,0,4,12,15,10,16,1,14,7,13,3,9,6,2,5,8,11}, + {18,29,28,24,30,25,23,27,31,20,22,34,26,33,19,21,32,15,5,17,4,11,10,3,7,9,16,2,13,1,12,0,8,6,14}, + {21,31,20,23,29,17,32,27,34,24,28,19,33,26,30,22,25,18,4,11,5,9,7,1,2,8,12,6,0,3,16,10,15,14,13}, + {18,22,20,27,34,31,17,23,19,29,21,32,24,28,30,33,25,26,11,6,0,3,8,4,2,9,5,15,7,14,10,16,13,1,12}, + {25,28,24,21,32,20,33,22,27,30,19,31,34,29,18,26,23,9,4,1,2,10,16,14,13,7,15,5,3,8,11,17,12,6,0}, + {19,23,21,18,28,25,33,27,24,20,26,22,31,30,32,34,29,11,2,17,10,5,9,8,7,14,12,0,4,16,13,1,6,3,15}, + {32,19,28,34,20,26,30,25,23,21,31,27,33,22,24,29,18,10,12,3,15,6,13,0,16,17,2,1,14,11,4,8,7,9,5}, + {20,34,30,33,31,19,26,22,21,25,18,28,23,32,29,24,27,2,16,9,7,1,6,11,0,12,8,14,4,10,5,3,13,15,17}, + } + }, + { 36, { + {35,25,27,34,18,21,26,30,33,31,29,19,20,32,28,23,22,24,1,5,7,9,3,10,14,2,15,11,16,0,12,17,8,13,6,4}, + {31,23,28,25,33,29,22,19,35,18,27,21,32,34,24,26,30,20,16,4,3,15,11,10,0,12,14,13,8,7,17,6,2,9,1,5}, + {26,22,33,20,25,29,19,24,31,30,34,32,18,35,28,21,23,27,7,13,17,9,8,0,16,5,11,14,3,10,4,15,12,6,2,1}, + {24,29,20,31,22,35,27,34,30,28,26,33,25,21,32,19,18,23,17,12,11,2,6,3,8,5,7,16,4,13,15,10,1,0,9,14}, + {32,18,28,22,33,19,34,20,30,24,21,31,26,25,29,23,35,27,17,12,0,8,11,15,10,3,13,6,14,9,1,16,7,5,4,2}, + {24,26,30,20,27,23,22,28,25,33,32,19,18,34,29,35,21,31,10,13,4,15,3,0,7,16,5,12,11,9,14,1,6,17,2,8}, + {27,24,33,30,22,25,35,19,31,29,32,26,18,23,20,28,21,34,12,10,14,3,16,5,7,2,0,4,15,9,11,8,17,6,1,13}, + {24,29,35,26,25,27,34,31,19,32,28,23,22,20,33,18,30,21,10,15,4,7,3,6,9,1,14,0,12,5,17,16,13,11,2,8}, + {24,35,28,22,20,33,18,27,21,29,19,30,34,32,25,31,23,26,2,7,11,3,5,12,16,15,14,13,6,9,1,8,10,4,17,0}, + {28,27,31,35,23,18,30,34,32,29,33,19,26,24,20,22,21,25,8,11,1,10,17,2,5,6,4,16,9,3,14,12,15,0,7,13}, + {32,20,33,23,34,29,25,22,18,21,19,30,24,26,35,27,31,28,2,13,16,5,7,3,12,1,0,4,17,15,11,6,10,14,9,8}, + {32,29,34,22,24,19,23,33,20,30,35,21,27,31,18,28,26,25,16,2,12,8,0,6,1,10,13,9,11,7,4,3,14,5,17,15}, + {19,30,35,29,22,31,34,23,33,28,25,27,26,24,32,18,21,20,8,11,7,17,4,2,15,1,3,0,9,12,10,5,14,13,6,16}, + {26,30,35,31,20,18,22,21,25,28,34,23,27,29,32,24,19,33,0,6,3,12,9,1,10,2,15,4,7,13,11,8,14,17,5,16}, + {18,20,35,25,34,29,31,19,24,22,27,26,28,23,33,32,21,30,0,9,8,15,10,6,16,11,3,7,5,17,14,13,4,2,12,1}, + {21,20,33,35,19,25,31,28,18,24,29,26,34,27,22,30,23,32,8,4,1,0,14,9,13,16,2,12,7,10,15,5,17,11,6,3}, + {20,26,35,18,31,30,28,34,22,29,23,25,27,24,21,33,32,19,13,10,2,5,0,9,17,4,7,14,11,6,1,3,15,8,12,16}, + {27,30,35,21,28,18,20,24,22,31,23,29,19,33,26,34,32,25,9,3,6,1,14,16,4,0,8,5,11,7,10,17,12,15,13,2}, + {33,27,21,18,30,25,19,23,35,29,32,34,22,24,26,28,20,31,17,16,5,7,1,8,2,4,14,11,12,6,13,10,9,0,15,3}, + {34,19,27,22,30,32,18,25,35,21,20,31,33,28,24,23,26,29,11,7,3,15,0,2,13,17,8,16,5,12,4,1,14,10,9,6}, + {24,28,27,32,23,29,19,22,30,25,21,34,26,20,33,31,18,35,0,5,7,1,12,3,2,4,8,14,10,17,16,15,9,13,11,6}, + {30,21,18,32,34,26,22,28,25,23,20,31,19,33,35,29,24,27,4,17,3,13,7,15,11,5,10,1,6,12,8,14,9,2,16,0}, + {24,20,32,26,18,34,27,21,30,35,33,31,19,29,22,25,23,28,5,9,11,0,2,15,8,12,4,16,17,7,14,10,6,1,13,3}, + {28,21,27,35,20,25,24,30,34,22,31,33,32,29,19,26,23,18,7,11,4,8,15,9,1,0,13,6,2,5,10,3,16,12,14,17}, + {35,22,32,19,18,34,33,28,27,30,26,25,31,24,29,21,20,23,16,9,11,8,14,3,0,10,15,1,5,2,7,17,6,13,4,12}, + {22,29,21,31,27,20,32,19,33,24,26,35,34,23,28,30,18,25,2,0,7,12,8,15,3,14,10,1,5,16,13,4,6,11,17,9}, + {21,32,31,20,29,23,19,28,34,25,35,24,27,26,33,22,30,18,17,15,4,6,1,11,8,10,12,7,3,16,2,14,13,5,0,9}, + {31,18,34,22,21,35,27,23,20,30,33,32,29,25,24,26,28,19,17,0,15,13,2,4,11,8,16,10,5,7,12,3,1,6,14,9}, + {28,33,32,21,26,34,25,24,23,31,18,19,35,29,27,22,30,20,7,11,17,12,10,1,5,8,0,16,6,2,13,4,9,15,14,3}, + {27,22,28,35,24,33,32,30,18,29,20,31,26,23,21,19,25,34,12,16,14,17,6,4,9,15,8,10,2,1,7,3,0,5,13,11}, + {35,20,29,31,30,25,33,26,18,21,23,19,28,22,27,34,24,32,2,6,4,7,13,16,5,10,3,1,8,11,0,17,12,14,9,15}, + {21,34,31,27,22,29,32,20,26,35,24,28,23,18,19,30,25,33,0,16,15,1,17,8,13,2,14,5,7,9,11,4,6,12,10,3}, + {24,31,20,35,29,22,34,21,26,33,18,25,19,27,32,23,28,30,4,12,2,7,17,6,0,15,10,8,13,16,5,1,3,9,14,11}, + {30,22,35,19,34,28,31,26,20,24,27,18,32,29,33,25,21,23,1,12,2,0,15,17,5,10,16,13,4,9,11,8,14,6,3,7}, + {32,29,27,22,34,20,21,19,18,25,35,31,33,30,28,24,26,23,6,5,1,16,7,12,10,2,15,11,9,13,4,0,8,3,14,17}, + {27,34,22,18,21,19,30,25,29,32,31,33,20,28,26,24,23,35,13,5,17,4,8,12,2,16,9,1,14,0,6,15,11,10,3,7}, + {29,22,33,24,27,18,20,35,31,28,19,26,30,34,25,23,32,21,10,0,9,17,6,15,1,11,4,7,12,2,5,8,16,13,3,14}, + {21,27,22,31,23,25,29,24,19,35,26,28,30,32,34,18,20,33,0,8,13,5,3,9,14,11,15,1,7,16,12,6,17,10,2,4}, + {28,20,33,24,26,34,32,29,31,22,25,35,30,27,19,23,21,18,9,14,11,0,17,8,12,10,7,13,4,6,1,15,2,16,5,3}, + {29,23,20,24,27,30,19,25,34,22,26,35,32,28,31,21,18,33,2,11,17,0,8,13,15,6,16,10,4,14,5,3,12,9,1,7}, + {22,26,28,21,25,29,19,18,32,27,34,31,35,23,33,20,30,24,17,6,9,1,3,2,0,10,16,14,13,4,11,8,15,12,5,7}, + {23,33,21,32,35,26,31,27,20,24,30,22,28,34,29,25,19,18,17,4,0,10,8,5,11,3,6,14,9,15,2,13,7,12,1,16}, + {25,27,29,35,33,18,20,23,21,26,28,32,31,24,34,30,19,22,13,2,15,9,1,16,6,11,7,0,12,8,5,10,4,14,17,3}, + {19,24,31,23,26,35,33,20,34,25,28,32,29,27,30,18,22,21,17,13,11,4,8,15,3,14,2,12,0,16,10,6,5,1,7,9}, + {26,34,18,35,25,29,33,32,24,27,31,23,19,28,30,21,20,22,3,0,2,12,8,7,9,4,13,16,11,10,1,15,14,6,5,17}, + {20,26,29,27,34,24,23,21,25,33,32,22,35,30,18,28,31,19,16,5,3,12,2,4,7,1,13,15,11,10,6,14,9,0,8,17}, + {22,25,35,21,24,23,33,27,29,32,30,19,20,18,31,26,28,34,13,2,1,9,8,10,4,15,7,6,11,3,12,5,17,0,16,14}, + {18,24,26,28,27,22,29,20,19,21,32,25,35,23,31,33,30,34,2,15,14,5,0,13,7,4,17,11,3,6,1,12,10,8,16,9}, + {23,22,20,29,27,30,19,33,26,21,31,35,28,32,24,34,25,18,9,6,10,0,11,4,13,16,14,15,8,7,17,2,1,12,5,3}, + {22,25,26,35,18,34,32,29,28,24,19,27,23,21,30,20,33,31,1,8,5,13,17,12,9,16,2,11,14,7,10,0,6,4,15,3}, + {28,19,35,31,20,27,22,18,25,30,24,33,21,34,29,26,23,32,17,13,4,2,16,8,5,14,9,11,7,0,12,1,15,6,3,10}, + {33,20,29,23,26,21,32,34,30,27,24,19,25,22,31,28,35,18,8,13,16,5,11,10,2,0,4,1,17,3,15,9,6,14,7,12}, + {19,32,24,21,26,34,25,28,31,20,22,30,27,35,33,18,23,29,17,0,10,3,9,15,13,1,4,6,12,8,11,2,7,14,16,5}, + {30,19,35,34,18,22,21,29,20,25,32,31,33,24,28,27,23,26,15,10,12,7,9,11,1,8,13,2,5,17,14,0,3,6,16,4}, + {35,26,34,22,30,31,28,24,23,20,25,29,21,19,27,33,32,18,0,8,17,12,4,9,1,15,3,14,6,13,11,7,2,5,16,10}, + {25,28,21,29,23,33,27,20,35,34,32,22,24,19,31,26,30,18,0,2,13,10,9,5,7,16,3,11,1,17,4,12,8,15,6,14}, + {31,33,24,20,23,32,30,35,27,25,19,21,29,26,28,22,34,18,10,6,3,2,9,4,16,15,7,17,1,0,14,11,8,12,5,13}, + {35,25,18,30,26,21,19,31,32,20,27,23,28,22,33,29,24,34,17,10,1,16,4,6,11,13,8,5,7,15,3,2,14,9,12,0}, + {31,35,30,28,18,24,21,23,32,22,19,34,25,26,33,29,20,27,4,1,16,6,9,7,15,14,13,17,3,5,10,0,8,12,11,2}, + {31,30,32,24,34,20,26,25,28,22,21,33,27,35,29,23,19,18,0,13,10,6,14,4,7,9,2,15,17,8,3,5,11,16,1,12}, + {20,22,34,29,28,30,33,18,21,32,26,25,24,35,19,27,31,23,17,7,9,14,11,16,2,1,13,4,3,10,12,5,0,6,8,15}, + {35,23,33,20,27,18,24,22,30,32,31,34,25,29,28,21,26,19,3,15,9,13,2,0,6,14,12,7,17,10,4,16,11,8,5,1}, + {20,18,26,24,30,22,21,31,27,34,25,23,35,32,33,29,19,28,17,9,5,4,10,2,6,8,14,12,16,13,0,7,3,1,11,15}, + {26,32,27,19,30,34,25,23,29,20,22,24,35,31,18,21,28,33,13,2,1,4,8,10,9,11,6,16,5,12,17,3,15,14,7,0}, + {23,35,32,29,20,18,25,28,34,27,31,26,21,30,19,33,22,24,13,11,15,7,14,4,10,0,3,6,8,16,1,12,17,9,2,5}, + {28,32,34,22,21,25,24,30,19,23,20,26,27,33,35,29,31,18,17,8,7,16,15,12,6,1,11,9,0,3,5,4,10,13,2,14}, + {21,35,31,27,33,29,25,28,24,26,34,18,32,22,30,23,19,20,8,0,6,3,9,4,7,14,13,17,16,5,15,11,2,10,12,1}, + {23,26,22,20,27,32,21,33,18,24,31,25,34,30,28,35,19,29,6,9,8,3,10,16,7,13,15,0,5,4,1,12,14,2,11,17}, + {35,32,34,30,25,28,22,31,27,26,33,19,23,29,21,20,24,18,8,16,9,7,0,12,13,2,5,4,11,10,3,6,1,15,17,14}, + {23,20,19,21,28,34,22,24,33,18,32,26,31,30,27,25,29,35,16,0,12,17,8,11,9,13,7,14,1,15,5,10,2,6,4,3}, + {30,22,32,27,33,23,26,19,28,25,29,21,24,20,34,18,35,31,11,0,9,3,12,6,14,10,17,5,4,15,1,13,8,2,16,7}, + {32,20,31,19,33,35,18,28,26,22,30,27,24,34,23,21,25,29,16,13,7,14,6,4,9,15,3,1,11,17,10,12,0,5,2,8}, + {26,19,18,29,23,31,25,24,34,27,30,22,35,21,28,33,32,20,17,14,4,11,9,8,15,3,7,1,10,6,13,0,16,5,12,2}, + {19,35,29,31,25,34,32,20,22,24,30,28,33,21,18,27,23,26,10,14,13,8,1,3,12,4,15,2,6,0,11,5,17,7,16,9}, + {31,19,27,18,22,35,21,32,28,33,25,30,24,23,34,26,20,29,7,1,8,6,4,13,12,3,15,2,16,10,11,0,17,9,14,5}, + {34,28,32,25,31,20,35,30,22,29,21,18,23,27,19,26,33,24,17,11,4,8,13,12,16,7,0,6,1,9,5,15,2,10,14,3}, + {30,27,24,20,25,33,19,35,18,32,23,31,26,21,29,22,28,34,11,10,2,8,6,17,5,3,16,15,14,13,9,4,7,12,1,0}, + {25,27,20,34,33,21,35,30,28,18,31,24,32,29,22,19,26,23,2,13,15,12,6,14,9,0,3,7,5,11,1,4,8,17,16,10}, + {25,19,34,27,29,23,31,18,33,20,22,35,24,30,28,21,26,32,15,0,9,12,8,16,7,3,13,17,14,5,2,11,1,6,10,4}, + {33,29,34,27,26,28,23,21,31,30,25,20,19,32,24,22,35,18,6,9,3,0,7,2,16,13,4,14,17,1,12,8,5,15,10,11}, + {21,18,28,35,27,32,20,34,33,19,22,31,30,29,23,25,24,26,17,12,0,9,11,2,10,13,1,5,3,14,4,7,16,15,6,8}, + {26,35,20,27,30,29,31,24,28,33,22,25,23,32,34,19,18,21,0,3,12,2,14,6,11,10,4,15,1,13,8,7,17,16,5,9}, + {20,30,25,28,32,23,27,31,33,26,35,24,19,34,29,22,18,21,7,3,9,5,1,4,0,6,13,12,14,11,15,17,8,16,2,10}, + {28,20,23,25,22,34,19,27,35,33,31,26,30,21,32,24,18,29,17,15,13,7,16,14,11,6,10,5,12,1,9,8,2,4,3,0}, + {35,21,24,33,20,30,34,31,26,23,29,27,22,19,32,18,28,25,17,12,9,8,7,10,2,0,13,1,6,11,5,15,3,16,4,14}, + {33,19,23,22,28,35,30,25,32,34,24,26,31,27,20,29,18,21,12,16,14,0,11,15,10,4,13,6,17,9,2,8,7,5,1,3}, + {18,33,31,35,26,29,23,22,34,20,27,19,28,30,25,32,21,24,8,1,2,11,5,14,17,13,4,0,3,10,9,12,6,16,7,15}, + {28,19,27,32,26,18,20,34,29,33,25,24,31,30,21,23,35,22,15,10,13,6,5,12,2,9,14,4,16,17,7,1,3,8,0,11}, + {33,28,19,31,22,35,30,26,20,24,21,27,25,18,34,29,23,32,9,2,11,8,0,6,1,3,5,13,17,4,7,10,15,14,12,16}, + {35,34,29,31,18,32,30,33,21,28,27,22,19,26,24,20,25,23,4,11,1,3,12,0,15,5,2,10,9,16,8,6,13,17,14,7}, + {23,33,32,27,18,22,31,19,28,35,30,21,20,29,34,24,26,25,1,9,6,12,11,8,14,16,13,0,17,5,4,3,7,15,10,2}, + {20,22,35,31,21,28,27,30,29,34,25,23,33,19,24,26,18,32,17,11,4,10,6,16,9,12,15,8,1,3,5,7,14,0,13,2}, + {29,31,25,18,23,21,26,32,34,22,35,19,33,28,20,30,24,27,3,7,16,6,0,4,10,15,13,17,1,9,8,5,2,12,11,14}, + {19,28,18,35,26,23,31,34,21,25,22,33,30,29,27,32,20,24,5,13,10,8,4,15,14,7,11,0,3,17,16,6,2,1,12,9}, + {31,19,32,25,34,23,18,29,21,24,27,33,26,30,28,20,35,22,3,0,9,6,5,10,2,16,13,8,4,1,15,11,17,7,12,14}, + {19,23,30,25,33,29,31,21,24,22,34,28,32,20,27,18,35,26,15,0,11,3,9,1,13,16,8,14,17,5,2,6,12,4,10,7}, + {22,32,29,33,28,27,26,34,23,31,25,30,20,35,24,19,21,18,8,17,10,12,9,7,11,4,14,13,6,15,0,16,5,2,1,3}, + {34,20,29,23,19,32,30,27,24,33,35,22,31,21,28,26,25,18,4,16,6,3,7,11,8,13,15,1,0,9,12,17,5,14,2,10}, + {35,25,33,24,31,34,32,20,22,28,27,26,18,21,30,23,29,19,3,0,7,4,2,6,17,12,11,14,8,1,5,10,9,16,13,15}, + {21,33,30,28,35,23,25,20,32,31,18,34,29,22,26,19,24,27,12,10,1,3,6,5,15,17,8,0,7,13,9,16,14,11,2,4}, + } + }, + { 37, { + {36,29,33,35,21,24,26,31,34,20,30,19,25,22,27,32,23,28,1,18,11,3,13,15,5,4,2,14,0,8,10,7,6,16,9,12,17}, + {23,28,18,20,29,31,27,25,30,34,36,22,21,26,33,35,32,24,19,7,11,4,12,5,1,16,3,10,14,17,15,8,13,9,2,6,0}, + {24,27,21,19,34,32,25,31,29,26,30,36,35,28,33,22,18,20,23,9,2,7,17,0,5,3,16,14,8,10,6,13,12,1,15,11,4}, + {33,22,36,23,18,31,25,28,27,35,34,24,26,30,21,32,29,19,20,1,14,10,16,7,2,5,12,11,8,15,9,0,4,6,13,3,17}, + {28,22,27,31,19,25,30,34,24,36,20,35,33,29,32,21,26,23,10,7,3,5,0,18,6,13,2,4,16,11,8,1,9,14,12,15,17}, + {36,28,18,21,23,31,25,19,34,27,29,33,32,20,24,22,35,26,30,5,10,14,9,7,16,11,17,3,6,0,2,1,8,4,15,13,12}, + {23,28,20,22,25,21,30,33,35,27,36,29,31,24,32,34,26,19,16,3,11,18,7,13,9,6,5,8,2,14,1,15,12,4,17,10,0}, + {22,25,19,27,35,24,32,23,31,26,30,34,33,21,28,20,29,36,17,2,10,13,16,6,12,11,7,1,5,0,4,9,3,14,8,18,15}, + {22,30,23,28,21,29,18,26,31,36,24,34,32,35,33,27,19,25,20,14,5,16,15,7,17,13,11,1,0,9,2,8,4,12,3,6,10}, + {27,29,24,33,28,21,32,30,23,26,34,22,19,18,36,35,20,31,25,9,10,13,2,1,12,3,11,4,0,15,8,7,6,16,14,5,17}, + {31,25,35,30,22,21,19,27,33,20,32,28,36,24,26,23,29,34,12,16,18,4,1,9,8,11,7,6,14,15,2,0,10,13,17,3,5}, + {25,35,30,32,22,20,27,36,18,28,23,26,21,29,24,34,31,33,19,3,10,13,12,2,0,9,5,4,8,7,16,1,15,6,11,17,14}, + {27,19,36,20,31,29,23,26,22,28,21,32,35,24,30,25,34,33,2,12,3,13,6,16,14,18,7,1,0,5,10,9,11,17,8,4,15}, + {32,29,22,25,27,33,28,19,34,24,23,20,36,21,31,35,30,18,26,8,14,10,7,16,0,15,2,13,5,12,4,3,6,1,11,17,9}, + {20,25,34,22,28,24,36,30,32,29,23,27,18,35,31,19,33,21,26,2,13,15,11,0,5,1,16,9,4,7,10,8,17,12,14,3,6}, + {21,19,32,35,25,36,30,24,18,28,33,31,22,20,34,23,27,26,29,16,6,0,12,5,11,8,10,2,1,9,13,4,3,15,17,7,14}, + {36,19,21,33,29,22,25,23,31,28,24,32,27,30,20,26,34,18,35,0,9,8,4,10,17,11,14,13,12,16,3,7,5,15,2,6,1}, + {19,21,34,23,27,24,31,36,26,22,29,25,35,28,33,30,20,32,3,10,2,1,7,15,17,16,4,18,8,5,12,6,14,11,0,13,9}, + {20,30,36,19,22,27,31,25,28,34,23,32,18,21,24,29,33,35,26,11,14,5,7,4,6,15,16,2,8,3,1,17,10,12,9,0,13}, + {23,32,24,33,36,20,26,19,22,21,28,34,30,25,31,27,35,29,18,1,5,12,8,4,3,9,13,15,6,2,10,7,14,17,11,0,16}, + {31,33,30,25,20,27,21,29,28,32,34,24,18,36,23,22,19,35,26,1,3,10,0,2,6,12,15,13,16,9,7,5,17,4,8,11,14}, + {33,20,27,30,19,24,31,25,29,21,36,32,34,28,35,26,23,22,15,11,14,6,1,4,12,9,17,7,13,8,3,10,18,0,2,16,5}, + {20,26,31,23,19,21,33,35,25,24,27,34,29,28,32,36,30,22,17,5,16,13,11,2,4,15,7,0,10,8,3,14,12,9,1,18,6}, + {25,21,35,19,30,20,29,23,26,32,28,31,18,36,22,34,33,27,24,6,9,1,16,8,10,14,17,11,13,15,4,0,5,7,3,2,12}, + {29,28,30,24,34,20,22,25,36,26,33,31,35,32,19,23,21,27,9,17,12,10,0,8,1,4,16,14,7,6,15,3,13,18,5,2,11}, + {23,30,20,35,29,33,22,36,19,28,27,18,26,31,34,32,25,21,24,12,16,0,13,8,6,15,7,4,14,10,1,9,2,5,11,3,17}, + {36,26,33,21,31,29,23,19,30,28,24,20,27,34,32,35,22,25,0,6,8,11,4,7,13,16,2,9,3,1,15,14,5,18,10,12,17}, + {20,26,32,36,22,31,35,34,23,25,21,29,24,28,27,19,30,33,14,12,5,4,13,6,2,17,11,18,8,3,16,15,10,7,1,0,9}, + {35,18,24,31,33,29,28,25,32,21,23,27,26,19,22,34,36,30,20,4,10,12,17,7,3,13,16,8,1,5,14,2,11,6,9,0,15}, + {30,33,24,19,35,34,23,29,31,36,26,28,25,20,32,22,21,27,17,12,6,18,10,7,1,15,0,4,11,8,14,16,13,3,2,5,9}, + {34,24,33,25,32,30,28,26,36,22,35,20,31,21,27,29,18,23,19,17,14,13,6,8,9,2,1,12,7,0,16,5,4,11,3,10,15}, + {21,25,32,34,23,31,22,19,26,24,36,33,27,29,28,35,30,20,16,14,11,8,13,1,6,2,18,12,5,7,15,9,4,3,17,10,0}, + {33,24,26,25,31,35,29,23,32,36,27,20,18,22,30,34,21,28,19,10,7,12,11,17,15,4,1,14,6,9,0,13,2,16,8,5,3}, + {31,29,19,35,24,27,33,32,30,23,26,25,22,34,36,21,28,20,3,2,17,8,11,14,13,9,12,0,10,1,6,4,16,15,5,18,7}, + {31,28,32,29,35,21,27,18,26,24,22,36,30,20,25,23,33,19,34,6,13,1,14,3,0,10,8,12,5,15,17,9,2,16,7,4,11}, + {19,22,29,33,21,24,20,32,26,27,25,36,30,28,35,23,34,18,31,0,6,14,17,15,5,12,10,2,13,9,16,1,7,3,8,11,4}, + {36,25,19,28,34,21,30,33,26,23,22,35,24,27,32,29,31,18,20,0,9,14,11,13,17,5,16,8,10,3,12,2,7,15,1,6,4}, + {25,19,26,21,31,22,30,29,24,20,36,32,28,33,18,34,27,23,35,0,8,12,11,6,17,16,2,10,5,1,9,7,3,13,15,14,4}, + {26,25,22,18,32,24,29,36,34,30,19,31,23,28,33,27,35,21,20,4,1,13,11,0,8,2,6,9,3,14,7,15,16,5,12,10,17}, + {25,31,22,34,27,26,24,28,35,30,19,33,36,21,20,32,23,29,13,12,0,6,3,9,14,11,17,4,1,16,7,10,15,8,18,2,5}, + {35,20,25,30,32,26,23,29,36,27,22,28,19,31,18,21,24,34,33,6,3,15,11,8,12,2,5,9,0,7,1,13,17,14,4,10,16}, + {31,30,26,29,19,18,27,25,35,34,33,21,36,32,28,24,22,20,23,9,2,11,0,15,14,7,16,3,5,17,1,13,4,10,8,12,6}, + {23,35,34,31,36,28,22,25,27,30,29,20,26,21,33,32,19,24,12,18,8,16,10,2,13,9,6,5,15,11,3,7,17,0,14,4,1}, + {34,22,36,23,20,30,33,21,26,31,28,27,35,32,25,29,24,19,5,7,16,13,6,0,11,1,4,12,17,3,9,2,10,15,18,14,8}, + {34,27,33,19,26,22,31,25,23,20,29,32,30,24,28,35,21,36,15,9,14,0,17,12,10,6,5,8,18,4,1,3,11,7,2,13,16}, + {20,34,31,26,24,27,21,33,28,32,36,29,23,35,25,30,22,19,3,5,13,11,9,12,7,14,18,6,15,17,8,10,16,4,1,0,2}, + {26,22,25,33,24,36,27,23,35,30,32,19,31,28,21,18,34,20,29,13,16,5,10,8,1,6,12,11,7,9,3,0,17,15,4,2,14}, + {32,21,29,33,19,34,23,26,20,24,28,36,22,31,35,25,27,30,0,2,12,17,5,7,11,14,10,16,8,13,3,1,6,9,18,15,4}, + {24,27,32,35,23,34,30,36,25,19,26,28,21,31,22,33,20,29,1,11,17,5,3,6,16,2,18,4,8,10,0,12,9,13,7,15,14}, + {24,34,36,27,21,25,30,31,26,33,29,23,28,22,20,35,19,32,3,15,11,4,13,12,2,9,16,6,18,8,14,1,0,7,5,17,10}, + {23,32,25,24,30,33,31,26,20,29,34,22,35,21,28,18,36,27,19,0,14,7,4,8,11,17,1,6,16,3,13,12,2,9,5,15,10}, + {32,29,18,20,22,19,21,35,34,33,24,36,31,26,23,25,28,30,27,14,1,6,9,4,0,8,12,10,7,16,2,17,13,5,3,11,15}, + {25,20,26,36,19,35,33,21,31,30,34,27,24,22,32,23,29,28,0,17,15,14,5,8,18,16,1,9,2,11,7,4,13,6,10,12,3}, + {25,20,27,30,35,29,21,33,24,22,34,31,23,32,28,36,26,19,2,18,3,9,14,1,15,5,7,6,0,11,8,13,10,12,4,17,16}, + {20,26,35,21,29,22,18,28,24,31,36,32,30,19,27,25,33,23,34,4,16,12,9,7,13,2,1,14,11,17,3,5,15,10,8,6,0}, + {25,32,19,36,24,31,21,30,22,35,34,23,29,27,26,18,28,20,33,9,7,6,1,11,8,10,15,3,16,2,12,5,13,4,14,17,0}, + {35,34,27,21,23,19,24,32,28,20,29,36,25,31,26,22,30,33,6,14,7,10,12,17,15,2,4,13,1,8,16,3,9,11,18,0,5}, + {22,31,24,20,23,34,27,29,36,28,35,21,30,32,25,33,26,19,9,18,7,11,16,8,12,14,5,1,3,17,2,6,0,15,13,4,10}, + {31,34,18,22,24,23,30,20,27,35,21,29,32,28,25,33,36,19,26,5,17,4,12,16,9,1,8,2,15,10,13,11,14,0,7,3,6}, + {34,20,35,33,19,32,25,36,24,27,21,31,30,29,26,28,22,23,2,13,7,0,8,5,3,16,14,1,4,9,12,11,15,18,6,10,17}, + {32,35,23,33,24,20,25,36,30,28,26,19,31,29,27,34,22,21,11,4,3,2,1,9,6,13,18,7,16,10,14,8,5,17,15,12,0}, + {21,26,19,31,27,22,20,35,33,24,28,32,36,29,23,34,30,25,16,8,15,1,17,4,14,11,10,6,5,7,13,3,2,18,9,12,0}, + {24,26,21,35,28,19,18,22,33,25,27,32,23,30,36,34,20,31,29,5,13,16,12,8,17,4,1,3,11,7,2,14,9,0,6,10,15}, + {25,18,33,32,24,21,31,35,34,36,27,23,22,29,20,26,28,30,19,12,17,16,11,0,8,13,10,2,6,14,7,3,9,15,1,5,4}, + {35,25,31,18,29,21,28,24,34,22,33,30,26,36,32,23,27,20,19,17,7,0,9,12,8,6,1,14,2,4,11,13,3,15,5,10,16}, + {19,23,34,32,35,22,20,24,28,33,29,31,25,21,36,27,30,26,2,4,6,16,13,7,18,17,15,10,8,12,5,11,3,9,14,1,0}, + {36,29,28,25,23,22,34,21,33,32,27,26,30,24,35,31,20,19,5,9,1,8,6,4,13,7,16,0,2,11,14,10,18,15,17,12,3}, + {34,29,25,21,26,22,28,31,33,30,27,35,20,23,36,32,24,19,9,4,6,11,16,1,17,10,0,3,7,14,8,12,5,2,13,15,18}, + {28,32,22,21,33,23,27,29,20,25,31,18,36,24,19,26,35,30,34,2,1,10,4,9,12,5,13,15,0,14,16,7,8,17,11,3,6}, + {20,30,34,19,22,26,21,33,23,25,29,32,31,35,28,36,27,24,8,12,6,11,10,15,5,1,14,16,7,18,2,4,9,13,17,0,3}, + {25,32,36,34,22,18,33,28,35,23,31,29,21,30,26,19,24,20,27,12,11,10,8,13,2,15,9,14,3,0,16,17,1,4,6,5,7}, + {29,21,28,20,18,33,36,22,25,32,23,19,31,24,27,35,34,30,26,11,17,1,7,10,16,8,4,9,2,0,3,12,5,14,13,15,6}, + {29,25,31,34,33,23,35,27,36,30,21,26,32,19,24,22,20,28,11,0,14,17,2,15,9,1,13,10,12,8,5,7,16,4,18,6,3}, + {30,21,18,27,33,35,23,25,29,31,20,34,22,36,26,32,28,19,24,3,16,10,7,1,11,4,8,13,0,17,14,2,6,15,12,5,9}, + {34,22,29,32,24,21,30,26,19,28,33,31,27,23,20,35,18,36,25,14,1,13,6,4,7,2,12,16,11,15,5,8,17,10,0,9,3}, + {24,33,23,29,32,35,20,27,30,18,25,28,26,36,21,31,34,19,22,10,1,3,5,12,8,16,11,6,13,7,9,0,17,4,15,14,2}, + {34,28,31,19,27,36,23,21,25,33,24,20,26,22,29,35,32,18,30,16,10,8,5,7,15,11,14,3,13,1,0,9,17,12,4,6,2}, + {24,31,25,32,35,23,22,36,26,18,30,19,29,34,20,27,21,28,33,16,5,1,11,13,8,15,0,2,9,7,6,3,12,17,10,14,4}, + {24,35,33,21,25,28,20,29,19,30,32,34,23,22,26,31,27,18,36,6,4,2,11,16,10,9,15,8,14,17,13,7,0,5,1,12,3}, + {36,24,20,32,34,22,27,26,29,31,25,30,19,28,23,33,35,21,4,6,15,16,1,18,14,17,7,0,9,11,2,5,8,12,3,13,10}, + {18,20,30,24,26,28,23,31,35,25,29,32,27,34,19,22,21,33,36,5,3,9,6,10,1,4,13,11,7,12,2,16,0,14,8,15,17}, + {26,21,23,29,27,22,31,28,32,20,25,19,33,30,35,24,34,36,1,7,18,15,0,11,17,12,8,14,9,4,13,16,2,6,3,10,5}, + {23,21,20,35,34,28,26,19,33,31,22,30,32,36,27,24,29,25,17,9,0,7,18,8,3,14,1,15,5,11,6,12,2,16,10,13,4}, + {34,33,32,35,26,22,19,21,25,27,29,31,30,23,28,18,36,24,20,1,13,16,4,14,2,9,11,6,3,5,7,10,15,17,0,12,8}, + {23,26,28,36,33,20,24,32,34,22,30,21,29,31,25,19,27,35,3,16,1,6,12,0,17,15,2,14,8,13,10,9,7,4,5,11,18}, + {20,28,35,30,36,22,25,34,21,33,24,19,32,31,26,23,27,29,2,11,6,16,5,1,14,17,13,8,3,0,4,10,12,9,7,18,15}, + {30,28,20,34,29,22,35,32,23,31,24,19,25,33,27,36,26,21,16,0,5,14,8,1,13,10,3,7,11,2,15,12,4,18,9,17,6}, + {23,34,20,33,27,19,32,26,36,31,28,22,24,30,21,29,35,25,11,9,5,17,3,12,10,15,14,8,2,13,16,4,1,7,6,0,18}, + {35,19,22,32,31,23,28,18,34,21,26,30,36,20,29,24,33,25,27,7,3,6,14,13,15,12,10,1,9,4,0,11,16,8,2,5,17}, + {21,33,25,20,32,19,36,30,23,31,27,34,18,26,24,22,35,29,28,14,6,1,8,16,5,0,3,11,17,12,10,7,9,15,2,4,13}, + {21,23,22,31,28,26,29,25,18,32,30,33,20,24,35,19,36,34,27,2,10,17,6,12,4,16,14,8,13,11,3,1,9,5,0,15,7}, + {20,32,26,30,33,24,19,34,36,25,23,21,18,29,27,35,28,31,22,6,0,4,7,13,1,16,9,15,17,12,5,2,11,14,3,10,8}, + {18,33,20,23,27,34,28,26,29,21,24,30,22,36,25,32,31,35,19,2,17,3,9,12,16,15,6,5,10,0,4,14,8,1,11,13,7}, + {22,24,36,31,27,30,32,26,35,34,23,25,21,19,29,28,33,18,20,8,7,6,1,12,4,14,3,16,9,11,2,17,10,13,0,15,5}, + {24,23,32,22,21,28,20,34,31,36,27,25,30,26,29,19,33,35,4,18,3,7,5,12,11,6,10,16,14,1,8,15,2,13,17,9,0}, + {25,19,28,30,33,32,31,34,24,20,36,21,26,22,35,27,23,29,6,14,7,15,11,17,3,12,2,5,16,9,13,1,8,10,0,4,18}, + {34,29,27,24,31,18,33,19,23,25,28,36,32,22,30,26,20,35,21,8,3,10,14,2,17,0,9,7,5,1,15,12,6,4,16,13,11}, + {34,23,29,27,33,32,36,31,20,25,30,21,35,28,22,19,26,24,17,13,16,10,7,4,3,8,11,14,5,18,2,1,15,9,0,6,12}, + {27,34,22,24,23,30,19,25,36,31,28,32,21,33,35,29,26,18,20,17,4,9,6,1,5,7,13,0,2,15,11,16,14,12,10,3,8}, + {22,32,20,26,23,21,29,35,30,27,25,36,33,28,19,24,31,34,17,16,5,8,3,13,6,18,11,2,9,4,10,15,1,7,12,0,14}, + } + }, + { 38, { + {23,19,36,24,28,30,29,33,32,26,35,27,34,31,22,20,37,25,21,8,16,18,13,9,4,10,7,2,0,11,15,6,3,1,5,12,17,14}, + {33,27,31,30,32,25,21,37,36,20,29,23,34,26,22,28,24,19,35,10,13,16,4,17,11,7,9,0,2,8,3,14,12,18,1,6,5,15}, + {19,27,35,30,26,34,33,31,20,32,24,23,22,28,37,21,36,29,25,16,11,14,12,7,18,5,4,0,6,17,10,15,9,3,8,2,13,1}, + {24,23,22,21,37,31,29,32,34,28,33,26,20,36,25,27,30,19,35,18,11,3,2,7,10,8,16,4,17,6,5,9,1,14,13,15,0,12}, + {27,34,20,36,26,19,23,30,33,29,35,32,31,25,24,37,22,21,28,2,14,6,15,0,5,12,11,7,13,10,9,1,16,8,18,3,17,4}, + {29,35,27,30,22,25,28,21,37,23,34,33,32,36,20,26,19,24,31,8,16,13,3,1,7,18,2,4,0,15,10,9,14,12,6,17,11,5}, + {36,31,29,21,34,32,23,28,25,22,27,26,35,37,33,30,24,19,20,2,9,5,0,18,7,11,8,16,4,13,10,17,14,6,15,3,1,12}, + {20,28,19,22,31,29,34,23,26,33,21,27,36,32,24,30,25,37,35,7,6,8,5,10,18,1,4,3,17,11,14,13,12,0,16,2,15,9}, + {20,37,22,19,34,21,24,27,29,23,25,36,33,30,32,26,28,31,35,3,7,12,16,6,17,4,15,5,13,0,10,1,11,18,2,8,14,9}, + {28,19,36,35,24,37,21,20,33,25,34,27,32,31,26,22,30,29,23,13,18,6,17,2,8,10,14,11,4,9,16,7,12,1,15,5,3,0}, + {32,28,24,36,20,37,35,30,19,31,29,21,27,33,25,34,22,26,23,17,18,16,0,8,4,9,12,1,3,14,10,2,7,6,11,5,13,15}, + {24,23,37,33,20,25,21,27,34,29,36,31,28,26,22,30,35,32,19,3,7,11,1,4,6,0,5,17,12,8,16,2,14,13,15,10,9,18}, + {22,36,26,27,25,32,23,21,24,29,33,35,31,37,28,19,30,20,34,11,6,9,0,12,14,2,16,10,8,1,7,4,3,5,15,18,17,13}, + {30,32,23,28,26,34,36,25,35,29,24,31,20,22,27,33,37,21,19,0,17,6,5,16,1,4,2,12,3,9,8,11,10,15,13,18,14,7}, + {28,20,33,30,21,34,26,29,27,32,31,35,22,25,24,19,37,23,36,13,15,5,7,6,16,3,9,2,8,1,11,17,14,10,4,18,12,0}, + {37,24,30,36,22,29,33,35,21,28,19,26,25,31,20,23,27,32,34,3,5,11,4,16,6,17,12,15,9,18,8,13,2,10,14,7,1,0}, + {33,21,28,37,29,19,26,32,20,24,36,30,27,23,34,22,25,31,35,11,6,9,15,4,18,16,8,2,10,7,5,17,13,0,14,1,12,3}, + {23,34,25,20,22,30,26,32,27,29,21,36,24,35,33,28,37,31,19,18,15,3,13,8,2,11,17,14,1,4,6,10,16,9,12,5,0,7}, + {27,36,25,37,23,35,19,30,32,29,24,34,21,26,33,20,28,31,22,8,13,7,18,1,15,0,11,3,5,17,2,10,12,9,16,6,4,14}, + {26,34,27,23,22,31,37,32,30,21,29,36,28,19,25,24,35,33,20,1,9,15,13,2,16,6,18,3,5,7,0,17,10,8,14,12,11,4}, + {33,25,30,27,37,34,28,35,20,23,21,26,32,31,22,24,19,36,29,2,5,10,12,1,17,15,8,7,18,16,4,3,13,0,9,11,6,14}, + {20,24,36,22,29,23,34,37,31,26,32,27,35,33,25,21,28,30,19,14,12,0,3,1,16,6,5,11,2,7,10,13,18,17,9,8,15,4}, + {20,27,31,28,25,32,24,30,36,22,29,34,26,33,21,37,23,35,19,3,15,14,6,2,16,13,5,4,8,0,17,12,7,18,11,9,1,10}, + {31,22,33,24,36,19,35,20,26,25,34,23,32,28,21,30,37,29,27,18,11,2,14,16,4,17,15,12,0,6,8,10,9,1,5,7,3,13}, + {32,30,27,36,34,33,22,21,20,26,35,37,23,29,28,25,31,19,24,1,7,16,4,15,6,10,3,0,17,11,14,9,5,12,8,2,18,13}, + {33,27,32,28,37,25,23,20,24,31,22,26,35,34,36,30,19,29,21,11,4,6,15,10,14,12,0,2,9,8,17,7,5,3,13,18,1,16}, + {31,37,21,23,32,28,35,34,24,19,36,26,33,22,20,27,25,30,29,18,11,0,4,14,10,13,5,9,2,16,6,12,15,17,1,3,8,7}, + {26,20,30,22,35,25,33,19,23,28,36,24,21,31,29,34,32,27,37,18,7,17,15,2,8,10,5,4,12,14,11,1,16,6,3,9,0,13}, + {24,36,20,30,35,37,25,33,22,32,31,27,19,21,28,23,29,26,34,12,10,13,6,15,0,8,17,11,14,16,3,18,9,7,2,4,1,5}, + {22,31,19,28,34,26,24,33,25,27,30,23,36,29,32,21,35,37,20,2,18,15,4,12,6,10,7,9,3,13,8,1,14,5,0,16,11,17}, + {30,28,31,24,35,37,20,32,21,34,19,26,25,36,23,33,29,27,22,8,14,5,12,15,2,6,17,1,4,10,9,0,13,3,18,7,16,11}, + {34,37,33,19,22,35,21,32,27,20,28,31,25,23,26,30,36,29,24,18,9,12,5,14,2,13,10,0,11,1,8,7,17,16,3,15,4,6}, + {31,36,32,35,19,26,37,28,33,29,25,21,30,20,23,22,24,27,34,15,10,5,4,9,17,18,1,6,2,13,7,12,16,14,3,8,0,11}, + {30,35,31,20,34,36,29,25,27,26,33,37,21,23,22,24,28,32,19,7,1,0,13,8,12,3,18,17,15,10,2,11,5,9,16,14,4,6}, + {23,27,24,31,29,26,25,37,28,36,35,30,33,19,32,34,20,22,21,15,10,9,2,13,6,1,7,16,4,0,17,12,11,8,5,18,3,14}, + {31,26,33,20,21,32,19,37,30,36,28,34,27,22,35,29,23,25,24,14,7,9,13,15,0,2,8,12,10,18,17,16,5,1,11,6,4,3}, + {26,25,29,36,23,21,28,19,24,31,20,22,30,35,37,33,32,27,34,6,17,4,8,18,1,15,5,10,13,3,12,14,11,9,16,7,2,0}, + {35,23,32,21,34,26,22,31,20,28,19,33,24,29,37,36,25,27,30,3,2,12,4,13,5,18,14,7,15,10,6,8,17,1,16,0,9,11}, + {34,29,25,20,35,32,31,37,26,21,19,22,33,23,36,24,28,30,27,0,9,8,13,7,1,10,3,11,18,6,15,12,16,5,4,2,14,17}, + {27,35,36,25,24,26,23,20,33,30,28,19,22,21,37,32,29,31,34,18,15,8,16,6,4,17,5,1,0,2,11,13,10,14,12,7,9,3}, + {34,26,21,33,29,25,20,19,32,31,22,37,24,23,30,27,36,35,28,17,9,3,16,0,11,5,14,10,18,4,6,7,2,15,13,1,12,8}, + {24,29,32,30,36,35,31,26,33,23,27,20,22,37,25,21,28,19,34,18,16,15,3,9,2,4,12,10,14,13,8,1,6,17,7,5,11,0}, + {23,30,32,27,22,28,31,34,24,36,33,37,20,35,21,29,26,25,19,5,1,16,13,10,0,9,17,7,11,12,3,2,15,8,14,18,6,4}, + {21,24,36,19,23,29,20,28,35,22,31,30,25,34,32,27,33,26,37,17,6,11,1,12,18,16,10,8,7,5,0,3,9,4,14,2,13,15}, + {36,34,22,33,32,25,24,20,37,19,30,26,31,27,21,28,23,35,29,14,10,15,18,9,0,16,2,4,17,5,8,6,3,1,11,13,12,7}, + {31,27,30,23,36,33,37,26,35,25,32,34,29,24,20,22,19,21,28,11,3,6,18,16,5,14,0,2,12,7,1,15,17,4,9,8,13,10}, + {31,19,36,30,24,37,28,25,34,27,22,21,29,26,35,23,33,32,20,10,4,12,16,1,8,6,2,9,11,5,0,7,14,18,3,13,15,17}, + {32,36,31,28,34,23,29,22,26,24,21,27,33,20,19,35,25,37,30,17,10,15,11,5,14,9,6,16,7,18,0,2,1,3,8,13,4,12}, + {27,37,34,30,33,25,36,21,31,24,19,32,26,23,28,35,29,22,20,3,0,16,1,11,10,12,18,2,8,6,14,4,7,9,5,17,13,15}, + {22,31,19,23,29,32,36,34,26,37,30,20,25,21,27,33,35,24,28,18,4,11,13,10,7,17,2,6,5,1,3,15,9,0,14,16,8,12}, + {31,27,29,25,28,34,30,35,20,23,26,24,36,32,22,19,33,37,21,18,16,1,13,6,2,5,14,4,3,9,8,11,0,7,10,12,15,17}, + {24,23,34,30,37,31,20,27,36,19,29,21,32,22,25,28,33,35,26,10,7,17,4,15,8,3,12,2,13,18,5,0,9,1,14,6,16,11}, + {32,29,37,20,33,28,21,36,34,22,26,23,31,24,35,27,19,30,25,18,17,2,16,8,1,10,6,15,12,5,7,0,14,3,11,4,13,9}, + {21,25,33,30,24,22,29,20,36,35,32,34,37,27,26,23,31,19,28,15,8,11,16,13,2,1,3,9,18,10,14,12,6,4,0,7,17,5}, + {22,25,32,28,27,33,37,30,19,26,29,34,20,23,35,24,21,31,36,18,13,3,14,5,2,9,17,10,15,8,16,1,7,12,0,11,6,4}, + {19,24,26,29,28,21,34,20,32,23,36,25,37,33,30,35,27,22,31,0,18,13,3,1,15,12,5,4,8,17,6,16,7,14,2,9,11,10}, + {25,21,32,28,35,26,34,36,31,27,30,24,33,29,20,23,22,37,19,13,18,11,6,5,7,10,0,14,1,9,8,3,15,2,16,12,4,17}, + {25,20,22,37,32,36,34,28,33,23,31,24,27,29,19,26,35,30,21,13,12,4,10,3,8,2,5,17,6,11,1,7,9,16,14,0,18,15}, + {29,37,36,21,30,22,24,35,28,34,23,19,31,25,32,27,33,20,26,13,4,6,5,7,16,11,1,8,15,10,2,17,14,12,9,3,0,18}, + {33,25,21,36,24,20,22,19,34,32,30,27,29,28,26,23,35,31,37,9,5,3,6,1,10,8,2,16,0,12,7,17,4,13,11,15,14,18}, + {27,35,26,20,22,21,19,24,31,28,25,23,36,37,29,34,30,33,32,18,14,10,2,17,13,11,15,7,9,8,16,3,0,6,5,4,12,1}, + {35,19,31,37,23,34,24,27,21,26,30,29,36,28,33,25,32,20,22,17,12,16,2,6,11,10,1,15,13,0,5,14,8,3,18,4,7,9}, + {21,35,26,24,20,25,19,37,33,32,27,29,23,30,31,28,36,34,22,7,11,15,9,17,3,2,0,10,14,16,5,13,6,8,12,18,4,1}, + {21,30,24,31,26,34,33,36,32,37,25,27,29,19,22,20,28,35,23,12,4,7,3,2,17,6,8,18,10,16,11,9,5,1,14,13,15,0}, + {21,27,32,36,22,25,28,34,33,35,29,26,23,31,19,37,24,30,20,4,6,11,3,0,16,15,5,10,9,2,17,12,14,8,18,7,1,13}, + {30,36,24,35,37,33,21,34,26,25,29,28,22,27,32,20,31,19,23,4,3,5,11,18,15,17,0,10,2,6,8,16,9,7,13,12,14,1}, + {31,22,21,33,24,35,19,29,25,27,20,28,36,26,32,23,34,30,37,11,10,18,13,15,16,8,6,4,2,7,17,0,14,1,9,5,3,12}, + {19,23,26,20,35,29,22,32,34,27,25,24,30,36,21,28,33,31,37,18,8,3,15,7,11,5,17,12,6,10,9,13,1,16,14,4,0,2}, + {30,36,25,35,23,32,31,28,26,37,29,34,22,24,20,33,19,21,27,18,13,0,7,10,1,5,11,2,16,15,9,14,4,8,6,3,12,17}, + {24,27,36,29,35,32,20,30,34,28,37,23,33,21,25,31,22,26,19,10,6,16,0,4,14,1,15,9,7,12,17,11,13,5,2,8,18,3}, + {29,24,20,27,36,22,31,25,33,30,32,34,26,28,35,37,23,21,19,0,13,9,11,16,7,17,8,4,2,18,6,14,10,1,5,12,3,15}, + {26,30,21,24,22,19,32,34,33,31,28,23,20,25,36,27,29,37,35,16,7,11,3,1,6,5,9,0,2,10,14,17,12,4,18,15,13,8}, + {33,24,27,30,19,32,37,35,20,31,21,34,26,22,36,29,23,25,28,13,18,11,0,7,1,15,3,8,5,9,6,4,12,17,14,16,2,10}, + {24,36,20,37,29,34,21,23,25,28,22,33,19,31,30,32,26,35,27,1,8,4,0,6,15,12,18,9,17,3,7,11,10,2,13,5,16,14}, + {19,23,25,31,37,21,33,35,27,22,32,20,24,30,36,28,34,26,29,0,13,5,9,11,7,10,16,15,17,4,2,8,18,6,3,12,14,1}, + {19,34,29,26,20,24,36,27,33,35,37,21,30,22,31,23,25,32,28,14,11,5,0,16,2,9,8,13,10,3,6,15,4,17,12,18,1,7}, + {26,23,33,24,30,21,27,19,22,37,28,36,25,31,34,32,20,29,35,1,11,9,2,14,17,12,4,8,15,16,0,13,10,6,18,7,3,5}, + {21,30,20,24,26,28,27,31,25,34,23,33,22,36,19,32,37,29,35,18,4,9,12,10,17,15,13,3,5,2,1,7,8,14,0,11,6,16}, + {30,26,31,35,33,24,32,23,36,25,29,21,28,37,34,20,19,27,22,14,7,3,5,12,10,6,13,15,0,8,17,16,18,1,11,9,2,4}, + {19,31,29,34,20,24,21,33,28,35,26,23,27,25,37,32,36,30,22,16,13,17,3,11,10,2,7,6,18,12,9,4,14,1,15,5,0,8}, + {30,37,23,31,26,20,27,25,36,33,22,35,29,28,24,21,19,34,32,18,3,9,0,8,6,11,7,12,16,10,15,4,13,1,14,5,17,2}, + {23,32,26,35,29,31,37,25,22,36,33,28,21,30,27,24,34,20,19,17,3,16,8,6,9,13,10,12,1,5,4,18,15,14,0,2,7,11}, + {35,24,19,33,30,32,34,22,20,37,27,29,31,25,28,36,23,21,26,5,10,4,14,12,15,13,18,8,11,7,9,2,1,3,16,0,6,17}, + {25,31,23,30,28,20,26,36,27,22,35,37,29,24,19,21,33,32,34,5,0,9,6,13,16,10,1,12,4,15,18,7,14,2,17,3,11,8}, + {20,22,25,32,19,28,23,30,33,36,31,24,29,34,21,26,35,37,27,1,12,9,3,10,8,2,6,16,13,7,15,18,4,0,17,5,14,11}, + {21,36,31,24,22,20,34,23,32,27,19,29,35,30,26,28,25,37,33,18,15,11,14,13,2,16,0,9,12,17,1,10,6,8,4,3,5,7}, + {30,20,19,21,33,24,35,29,22,36,25,34,27,28,32,26,37,23,31,15,11,17,13,18,0,7,14,10,8,3,12,6,16,5,9,2,1,4}, + {25,29,20,31,26,36,22,28,19,32,27,23,35,37,24,30,34,21,33,15,1,9,14,10,6,4,18,3,11,0,5,2,13,16,8,7,17,12}, + {20,24,27,26,21,32,34,28,33,37,23,25,35,30,36,19,31,29,22,3,7,11,9,6,1,13,17,5,12,15,8,16,2,10,18,14,0,4}, + {35,25,37,21,30,29,32,28,24,22,34,33,36,23,19,27,26,20,31,16,12,1,8,2,14,7,5,9,6,0,11,18,4,13,10,15,3,17}, + {36,27,34,28,35,37,30,21,33,29,22,20,24,32,26,31,23,19,25,4,3,7,10,16,0,18,14,8,11,9,15,2,13,6,12,17,1,5}, + {32,26,27,34,36,31,33,23,28,25,37,35,29,21,30,24,20,22,19,1,7,17,11,9,15,4,2,8,5,14,6,18,13,0,3,12,16,10}, + {28,19,37,29,21,31,26,23,22,20,36,30,33,35,27,25,24,34,32,9,15,13,16,3,11,8,17,12,4,6,14,1,7,5,10,2,0,18}, + {34,26,19,27,33,25,21,36,22,37,28,24,29,35,32,20,30,23,31,10,17,7,13,0,14,6,15,5,16,8,4,1,12,3,11,2,18,9}, + {22,34,21,19,27,36,35,37,23,29,32,30,28,31,24,33,26,25,20,18,15,11,13,5,10,1,16,9,3,8,7,0,14,6,12,4,2,17}, + {24,35,22,20,25,32,27,31,28,21,37,26,30,29,34,23,36,19,33,6,3,11,4,12,9,10,1,13,8,17,0,7,5,15,14,18,16,2}, + {25,20,31,21,36,24,37,22,34,19,35,26,28,33,29,23,32,27,30,1,5,7,3,10,15,9,14,4,18,13,8,17,16,11,6,0,12,2}, + {37,26,21,29,24,34,33,32,36,30,27,35,20,23,31,19,22,28,25,3,5,1,9,6,10,17,4,16,8,2,15,13,11,0,14,12,18,7}, + {34,31,21,37,33,30,29,36,32,24,20,23,26,35,19,22,27,25,28,14,1,8,16,0,10,6,3,9,18,2,11,15,5,7,17,13,4,12}, + {25,23,27,24,28,22,31,20,36,35,19,30,21,34,26,32,37,29,33,15,1,6,12,14,13,8,17,18,3,16,4,0,5,9,11,7,2,10}, + } + }, + { 39, { + {22,31,35,37,32,34,24,30,36,28,25,21,33,20,23,27,29,38,26,18,1,19,7,10,12,3,13,4,0,8,6,15,9,2,14,17,5,16,11}, + {37,33,24,21,32,22,25,30,19,35,34,29,31,28,23,27,38,26,36,20,0,11,16,10,6,1,3,7,17,13,5,12,2,18,4,8,15,14,9}, + {36,32,20,33,27,35,28,26,19,31,23,38,30,24,37,25,21,29,34,22,16,15,7,11,2,1,18,12,9,14,0,6,13,10,5,4,8,17,3}, + {19,26,28,35,23,29,24,34,38,32,37,21,36,30,33,25,31,27,22,20,17,14,6,13,15,10,12,3,5,9,0,11,16,2,8,1,4,7,18}, + {29,28,31,36,20,25,23,21,33,26,24,37,30,32,34,38,27,22,35,12,19,6,15,17,9,4,16,14,1,0,11,8,10,18,2,7,5,3,13}, + {28,25,36,23,26,35,34,27,22,20,33,21,38,31,24,30,29,37,32,18,9,12,4,13,10,1,5,8,7,16,3,19,15,6,14,17,2,0,11}, + {37,31,26,22,38,23,34,33,35,21,20,19,30,27,36,24,32,28,25,29,6,11,13,5,15,18,3,12,14,2,9,4,17,7,16,8,1,0,10}, + {36,38,23,33,27,32,26,21,24,28,30,29,22,25,34,31,20,37,35,18,17,2,13,15,1,6,16,19,12,3,9,14,0,10,7,5,8,11,4}, + {25,33,20,28,24,23,35,34,21,30,37,31,26,32,22,36,38,29,27,2,5,11,0,16,10,19,7,4,13,12,9,3,18,15,14,6,1,8,17}, + {35,33,30,20,24,34,32,23,29,22,38,25,37,21,28,26,31,27,19,36,1,17,16,18,13,5,11,4,8,14,9,2,6,7,15,0,3,12,10}, + {29,34,22,20,25,31,27,21,23,36,24,35,19,26,38,33,32,37,30,28,10,1,4,0,14,18,9,6,11,2,12,15,8,5,17,16,13,7,3}, + {35,26,34,22,20,21,33,37,23,31,27,29,36,38,30,24,28,25,19,32,1,0,3,16,8,7,6,14,2,12,15,10,13,4,11,9,18,17,5}, + {29,27,35,28,33,31,37,36,32,38,34,26,19,22,25,23,20,24,30,21,0,10,12,5,15,3,18,16,6,1,17,13,8,2,4,9,11,14,7}, + {35,30,22,33,38,24,21,26,20,28,36,25,27,29,32,37,23,31,34,1,8,5,17,16,13,0,19,12,14,10,15,4,9,3,11,18,7,6,2}, + {30,28,22,21,37,35,33,20,29,25,31,23,26,38,36,34,32,24,27,6,12,8,10,5,17,7,18,16,2,1,19,15,0,14,11,3,9,13,4}, + {38,22,37,25,34,23,35,32,28,26,29,33,36,31,30,21,24,27,20,3,18,4,1,6,15,7,9,11,8,2,13,5,10,17,12,16,14,19,0}, + {21,24,35,29,33,20,22,36,28,37,30,32,31,23,26,27,25,34,38,7,16,18,2,6,4,10,5,3,14,1,11,9,15,13,17,12,0,19,8}, + {30,35,28,20,27,23,29,33,37,32,22,21,31,25,34,38,24,26,36,10,9,15,19,12,16,11,17,1,13,6,0,18,3,7,14,4,8,5,2}, + {21,32,26,31,38,28,20,23,34,22,30,29,25,33,35,37,27,36,24,18,17,19,9,1,3,10,8,7,0,2,6,5,15,13,11,12,14,16,4}, + {26,20,35,37,22,25,28,21,33,31,27,23,36,24,30,34,29,32,19,38,13,4,17,2,9,11,1,8,15,10,0,14,7,16,5,18,3,6,12}, + {21,26,35,23,34,28,31,24,29,22,36,20,33,25,37,27,32,30,38,12,0,4,10,1,8,16,15,3,7,13,6,9,11,19,18,5,17,14,2}, + {33,24,26,36,19,22,32,35,23,31,34,38,21,29,28,25,37,20,30,27,10,12,5,0,3,2,6,16,14,17,15,9,13,11,18,7,1,4,8}, + {30,36,35,32,34,37,29,28,23,26,22,27,31,33,25,38,24,21,20,0,10,5,8,18,11,3,14,4,1,13,12,7,19,9,16,6,17,15,2}, + {34,22,33,20,26,28,31,30,36,29,37,35,24,38,32,25,27,23,19,21,9,1,8,2,0,14,17,11,4,18,5,7,13,16,10,3,6,15,12}, + {24,22,21,32,36,20,25,27,23,33,28,37,30,38,34,29,31,19,26,35,14,10,1,6,9,16,7,15,3,13,12,5,18,11,2,4,17,0,8}, + {37,22,30,26,38,36,24,34,32,31,21,29,28,27,25,35,33,23,20,4,15,13,18,9,19,1,8,12,10,6,11,5,7,3,14,2,17,0,16}, + {21,23,35,32,26,25,20,27,36,22,30,37,28,34,31,29,38,33,24,18,14,5,7,13,0,15,8,12,19,3,11,9,17,1,16,6,10,2,4}, + {22,20,38,34,21,31,29,33,23,19,25,30,26,35,37,28,24,27,36,32,17,2,10,18,7,13,3,14,11,16,1,9,8,6,5,12,0,4,15}, + {38,24,36,31,30,37,35,23,34,32,22,21,26,29,27,33,25,28,20,5,16,11,17,10,1,6,12,3,13,7,9,14,19,15,8,4,2,18,0}, + {25,21,35,22,34,27,30,19,36,33,37,32,20,31,29,38,24,28,26,23,8,7,14,12,15,10,3,6,11,1,18,17,4,2,0,16,5,9,13}, + {33,35,20,29,28,23,30,36,38,26,19,22,31,34,32,24,27,25,37,21,5,0,18,11,1,16,15,3,10,7,4,12,14,8,2,6,9,17,13}, + {30,37,25,33,29,38,21,36,22,26,35,19,24,20,27,32,23,28,31,34,13,4,8,16,7,2,17,11,9,14,0,18,6,3,5,1,15,10,12}, + {27,34,21,25,32,30,36,38,22,31,37,24,35,26,29,33,20,23,28,14,10,12,7,6,2,11,13,9,15,19,16,18,17,5,1,3,4,0,8}, + {32,21,24,30,33,36,29,23,37,27,38,25,35,19,28,34,22,20,26,31,3,16,9,15,13,10,8,7,18,17,4,1,11,14,0,12,5,2,6}, + {38,33,35,20,22,34,36,26,24,37,19,31,28,21,23,29,25,30,27,32,3,13,18,14,8,16,1,9,2,15,17,11,7,10,5,12,6,4,0}, + {38,23,22,32,29,35,21,33,25,30,27,20,26,36,28,31,24,34,37,8,3,17,18,5,14,7,12,10,0,4,15,9,2,19,6,1,13,16,11}, + {31,19,27,38,35,21,37,32,22,25,24,30,33,36,23,29,34,26,28,20,0,17,4,12,15,8,16,18,11,9,2,5,14,1,10,3,7,13,6}, + {32,34,26,24,35,37,29,22,38,21,30,25,23,20,27,19,28,31,33,36,0,9,12,11,1,7,2,6,16,8,10,13,17,15,3,4,18,5,14}, + {30,22,34,38,25,28,32,27,21,36,31,37,24,33,26,29,19,23,20,35,5,1,17,2,6,14,10,18,3,16,0,9,11,7,13,15,12,4,8}, + {28,22,33,20,31,36,32,29,21,27,35,25,24,23,37,34,26,30,19,38,14,9,15,3,17,5,1,11,8,2,12,4,18,16,7,10,0,13,6}, + {22,21,28,36,29,31,30,25,35,27,19,32,34,24,23,33,37,26,38,20,13,2,12,1,3,5,14,16,15,6,11,8,18,17,7,0,9,4,10}, + {21,26,25,34,30,37,27,23,29,32,24,22,35,28,38,33,20,36,31,11,2,8,12,10,19,13,0,18,6,17,4,14,9,3,15,7,16,5,1}, + {36,32,27,31,28,38,22,29,20,25,37,30,24,33,23,26,35,34,21,10,16,18,4,11,17,0,8,15,6,12,3,14,7,1,5,13,2,9,19}, + {30,29,37,21,34,33,35,27,32,31,36,22,25,24,28,23,26,38,20,6,19,11,5,13,4,10,8,14,18,2,17,12,0,16,15,3,1,7,9}, + {23,30,24,38,22,29,33,26,28,31,35,25,32,34,36,27,37,21,20,0,2,17,6,1,5,4,8,3,15,9,16,19,12,11,13,18,7,10,14}, + {37,19,24,36,22,21,20,38,26,33,35,27,25,32,29,23,28,31,30,34,3,18,11,17,16,2,1,0,7,10,15,6,8,13,12,5,14,9,4}, + {34,21,36,28,23,37,32,29,33,26,20,27,24,25,19,30,38,31,22,35,2,1,3,7,15,4,0,13,5,18,9,11,6,12,8,16,10,17,14}, + {23,34,28,25,29,27,30,37,21,26,22,31,38,35,24,33,32,36,20,18,19,6,17,15,2,4,16,11,5,10,12,8,14,7,3,13,1,0,9}, + {29,35,20,32,21,23,26,33,31,27,30,28,34,37,25,24,22,36,38,18,6,0,15,10,13,4,17,2,1,11,9,7,12,8,14,3,16,19,5}, + {34,28,37,23,22,29,35,20,36,32,27,38,31,21,26,25,30,33,24,18,19,2,12,5,15,4,17,14,8,7,1,9,13,6,16,0,11,10,3}, + {27,29,28,35,23,21,20,38,31,37,22,32,19,34,36,26,24,33,30,25,1,5,2,16,4,6,15,18,10,0,13,3,11,17,12,8,14,9,7}, + {30,20,27,29,22,24,21,33,28,26,31,37,36,34,25,32,38,35,23,15,19,16,11,14,10,6,0,7,1,18,17,5,4,12,9,2,13,3,8}, + {25,24,19,28,26,32,21,29,34,38,31,23,27,37,33,35,30,22,36,20,17,4,18,3,1,16,13,7,2,10,9,8,12,0,15,14,6,5,11}, + {26,38,35,22,21,37,33,27,34,32,29,25,30,20,31,36,24,23,28,8,2,13,7,11,9,3,16,18,14,5,12,17,4,0,6,15,10,1,19}, + {26,37,35,38,28,32,20,24,36,23,29,33,30,25,21,31,34,27,22,18,4,16,7,10,5,0,15,9,6,2,12,19,13,17,1,3,14,8,11}, + {34,20,35,38,33,21,24,30,26,29,22,25,23,36,32,28,37,27,31,9,2,8,19,14,4,13,18,10,12,1,0,5,7,3,6,17,15,11,16}, + {21,38,29,24,36,27,34,26,35,22,31,25,33,19,37,30,32,23,28,20,7,18,17,3,1,9,13,12,0,4,2,15,5,11,16,14,8,10,6}, + {38,34,23,33,27,22,35,29,31,24,28,20,19,26,30,36,32,25,21,37,5,0,18,2,16,10,13,4,9,7,1,8,12,15,14,11,3,17,6}, + {21,38,25,28,22,33,19,23,34,26,24,36,29,31,30,32,35,27,37,20,7,15,1,18,13,17,8,2,10,9,11,14,5,16,4,3,12,0,6}, + {22,25,27,30,29,32,35,24,31,33,37,26,38,21,34,36,28,23,20,3,13,8,0,19,11,5,7,12,9,17,16,14,10,1,6,4,15,18,2}, + {28,26,29,20,37,33,23,38,34,32,22,24,21,25,30,27,36,31,35,3,0,15,6,5,10,8,12,2,18,9,13,7,17,1,19,4,14,11,16}, + {29,23,34,27,30,36,25,32,24,22,35,21,33,31,20,38,28,26,37,3,14,2,7,15,1,9,5,19,4,13,11,6,0,10,17,12,18,16,8}, + {30,36,29,28,26,33,19,21,35,24,32,38,23,27,31,22,34,25,37,20,9,18,1,0,12,3,6,14,10,5,7,13,15,11,16,8,17,4,2}, + {28,27,31,21,26,30,34,22,24,23,38,19,37,33,20,25,32,29,35,36,8,15,12,2,9,3,0,18,10,16,13,4,7,17,11,14,6,5,1}, + {20,27,31,29,25,30,32,36,34,22,37,24,33,38,26,23,21,28,35,7,19,2,17,4,11,5,14,1,9,8,10,18,0,12,3,15,6,16,13}, + {22,36,31,38,25,30,24,26,34,33,21,29,27,23,32,28,37,35,20,0,19,10,15,7,18,4,3,1,13,9,11,6,8,16,12,14,17,5,2}, + {38,29,32,19,23,28,36,20,35,27,34,24,31,22,25,37,33,26,30,21,17,6,3,18,13,10,12,1,4,15,7,16,9,14,2,11,5,8,0}, + {38,32,34,28,30,29,21,31,36,35,24,37,20,22,33,25,27,23,26,4,15,6,18,5,10,9,17,3,14,8,0,7,16,2,12,19,13,11,1}, + {19,36,38,31,22,32,28,34,20,35,21,25,23,27,37,29,33,24,30,26,16,9,2,5,1,17,0,4,3,8,12,10,13,15,7,11,14,6,18}, + {29,34,30,25,36,31,24,35,26,23,20,38,22,32,37,33,27,21,28,10,18,1,8,17,3,5,19,11,2,15,13,16,9,6,0,14,4,12,7}, + {22,25,32,35,23,29,34,27,26,24,38,36,21,37,33,31,28,20,30,16,19,0,3,15,11,17,13,8,14,9,12,10,2,4,6,5,7,1,18}, + {30,34,37,20,25,23,31,38,24,32,36,22,21,28,27,35,26,33,29,0,11,5,2,19,6,16,9,7,1,3,12,4,15,17,13,8,10,18,14}, + {19,37,35,30,38,22,21,34,33,36,28,31,23,26,20,24,29,25,32,27,0,12,16,6,1,17,7,18,10,3,2,9,14,11,5,13,4,15,8}, + {34,31,29,32,28,26,36,30,19,27,37,22,21,38,20,23,25,35,24,33,6,10,5,15,18,2,4,3,0,7,9,8,16,11,14,12,17,1,13}, + {23,28,27,32,35,37,34,20,31,26,24,21,29,25,38,36,33,30,22,15,13,0,3,1,18,7,6,9,16,14,5,19,2,11,4,8,12,17,10}, + {19,28,37,21,29,20,22,27,30,36,24,38,31,34,23,26,35,32,25,33,18,17,2,13,10,0,4,16,11,9,1,14,12,7,3,6,8,15,5}, + {26,32,20,35,27,34,22,24,38,31,21,23,33,36,30,29,28,37,25,15,2,17,13,10,14,9,12,18,16,1,6,4,19,0,3,5,7,11,8}, + {26,24,34,36,27,38,29,21,32,30,33,22,35,19,37,23,31,25,28,20,2,4,16,10,1,8,3,14,5,17,15,6,13,9,11,18,0,7,12}, + {38,27,21,26,25,32,23,19,35,37,34,33,22,28,30,36,24,29,20,31,12,6,1,16,18,8,2,0,5,15,11,7,4,9,3,14,10,17,13}, + {30,26,32,28,27,37,33,29,23,21,20,24,36,38,31,35,34,19,25,22,7,2,4,9,16,3,1,0,17,8,5,14,10,12,11,13,18,15,6}, + {32,31,21,34,38,28,25,37,20,35,22,30,33,27,26,29,36,24,23,14,16,8,10,3,2,5,18,0,13,7,6,11,4,17,1,19,15,9,12}, + {24,32,21,36,23,25,29,33,38,35,37,34,20,22,26,28,27,31,30,19,12,2,13,11,18,15,14,1,8,3,0,17,16,7,4,9,6,10,5}, + {38,32,35,31,20,30,23,34,21,29,25,33,27,37,19,24,36,26,22,28,4,8,5,15,0,12,10,9,14,7,1,17,6,11,3,2,16,13,18}, + {37,36,28,24,22,29,30,27,33,31,38,34,20,35,19,26,23,32,25,21,1,17,9,7,3,18,15,5,2,12,11,14,13,8,6,10,16,0,4}, + {30,32,27,23,37,29,31,36,38,26,22,20,24,34,25,28,35,33,21,18,5,12,10,1,11,17,9,7,2,19,3,15,13,16,14,4,8,6,0}, + {36,33,21,31,37,29,32,22,30,28,27,34,23,26,25,35,38,20,24,18,0,3,2,5,19,12,16,14,4,15,1,11,7,13,9,8,17,10,6}, + {38,21,33,31,24,29,36,25,34,20,23,32,28,26,35,37,27,30,22,8,6,3,14,12,19,18,5,4,1,17,11,10,15,13,7,0,9,16,2}, + {32,37,25,20,33,27,19,36,22,35,29,26,34,21,38,31,24,30,28,23,1,0,4,14,16,5,15,6,10,7,13,18,11,9,8,12,2,17,3}, + {28,25,38,29,35,27,30,33,20,37,32,31,21,34,23,22,26,36,19,24,8,11,7,12,10,3,6,17,4,1,14,18,16,15,13,0,2,9,5}, + {30,32,28,36,25,35,22,38,24,27,26,23,21,31,29,37,34,33,20,7,6,15,1,11,8,14,10,4,16,9,19,12,17,3,2,18,5,13,0}, + {22,20,23,32,38,29,31,26,34,37,33,21,36,28,30,25,27,24,35,18,4,2,0,16,12,9,15,14,11,13,8,19,5,7,10,6,17,3,1}, + {24,34,27,30,28,20,31,26,22,25,38,29,36,33,37,23,21,35,32,18,17,0,7,2,5,12,4,13,3,10,16,19,11,6,8,14,9,1,15}, + {20,24,33,30,21,34,32,38,36,25,22,35,29,37,23,26,28,31,27,11,0,19,10,1,14,7,15,3,18,13,16,9,6,2,8,4,12,5,17}, + {37,19,23,35,27,24,26,21,29,25,31,20,32,38,36,33,22,28,30,34,8,13,12,14,17,6,3,2,7,0,18,10,1,16,5,15,11,4,9}, + {37,26,22,30,34,36,32,38,31,27,35,24,33,28,20,25,23,21,19,29,16,9,4,8,7,13,17,3,1,11,0,10,14,12,15,6,18,2,5}, + {34,33,36,28,31,21,38,25,30,27,35,22,19,29,32,26,20,24,23,37,0,9,7,3,1,15,14,8,17,13,11,2,5,18,4,12,6,16,10}, + {32,19,21,38,35,31,23,33,28,29,24,37,27,26,34,36,25,30,22,20,10,17,13,15,3,1,18,12,8,0,11,5,9,7,14,4,6,2,16}, + {19,26,36,31,37,24,30,21,32,35,29,38,28,27,25,34,22,33,20,23,16,18,11,15,2,14,0,5,4,9,1,10,17,6,3,7,12,8,13}, + {35,32,37,23,21,34,28,25,27,24,31,26,38,29,33,36,30,20,22,3,1,4,10,19,6,11,9,12,16,18,5,2,7,14,17,0,15,13,8}, + {24,27,37,28,38,29,34,25,23,33,30,21,31,35,22,20,32,36,26,6,2,16,9,14,8,12,4,3,17,18,15,19,10,1,5,0,7,13,11}, + } + }, + { 40, { + {39,20,37,36,28,34,38,33,32,25,35,26,24,27,23,30,29,31,22,21,17,9,11,14,2,15,13,3,0,7,6,10,12,19,18,4,16,8,5,1}, + {24,30,27,34,29,26,25,28,21,38,22,35,37,23,32,39,36,20,33,31,16,4,2,9,8,13,5,0,11,6,18,10,1,12,14,19,7,15,3,17}, + {26,36,33,32,39,38,29,23,35,37,20,30,27,24,31,21,25,28,22,34,16,3,14,8,5,10,15,13,17,0,6,4,18,12,9,7,11,2,1,19}, + {33,29,26,34,20,27,35,24,22,32,37,21,39,36,30,23,25,38,28,31,19,9,12,0,18,6,10,15,13,5,7,3,16,11,14,8,2,1,4,17}, + {38,35,29,20,25,34,30,24,33,39,23,21,27,31,26,22,28,36,32,37,19,11,3,14,8,18,9,6,0,4,2,1,15,5,16,12,17,10,7,13}, + {34,33,22,20,37,35,25,39,27,24,31,26,38,36,32,23,29,28,30,21,10,16,8,1,5,11,13,0,2,4,3,12,6,18,7,19,14,9,15,17}, + {21,33,23,32,34,26,39,20,27,24,35,37,31,28,36,29,22,38,25,30,14,3,17,8,15,10,2,18,13,19,9,12,4,7,1,5,11,16,0,6}, + {36,26,39,24,31,29,34,32,23,33,28,30,38,22,37,27,25,35,21,20,17,10,4,15,1,12,19,16,8,7,14,3,2,6,5,9,0,11,18,13}, + {26,28,22,24,33,35,23,25,30,34,38,27,21,36,39,29,37,32,20,31,4,2,6,17,19,15,0,13,9,12,18,5,11,3,16,14,7,1,10,8}, + {27,31,35,37,39,26,23,29,33,20,22,21,36,28,30,38,25,32,34,24,18,0,2,15,17,5,7,16,11,13,9,6,12,14,3,8,1,4,10,19}, + {38,20,33,35,23,21,29,22,34,28,32,31,37,39,27,30,24,36,26,25,4,8,15,6,5,19,13,11,9,2,17,10,7,3,12,1,14,18,0,16}, + {20,32,31,28,33,38,37,36,30,29,27,24,22,35,23,34,25,39,26,21,15,13,11,17,12,5,10,9,16,2,4,1,18,14,19,7,6,0,3,8}, + {22,38,27,23,21,33,31,25,39,32,36,30,34,29,37,24,28,26,35,20,4,16,2,14,6,3,12,8,13,5,10,15,11,0,19,18,1,17,9,7}, + {20,27,23,34,31,29,39,25,38,35,37,32,26,22,36,24,28,30,33,21,15,11,14,18,19,12,7,13,17,3,10,4,2,16,0,5,9,1,6,8}, + {24,21,27,29,32,23,38,22,37,36,34,39,26,35,25,30,33,28,31,20,16,4,10,3,11,18,6,2,0,19,8,13,12,14,1,9,5,7,17,15}, + {26,22,27,39,30,35,20,31,38,29,25,23,21,34,36,33,24,28,37,32,10,14,11,0,17,12,9,19,6,3,7,4,16,5,13,18,8,1,15,2}, + {20,30,23,36,29,35,22,28,37,33,24,27,39,32,26,31,25,38,21,34,12,8,11,6,19,10,4,14,5,13,1,18,15,9,3,7,16,2,17,0}, + {23,39,25,34,27,33,30,38,26,31,37,22,29,28,36,32,24,35,21,20,19,11,10,0,3,13,8,12,15,4,6,18,17,5,1,14,2,16,7,9}, + {29,36,38,28,34,26,30,32,35,39,22,20,25,23,37,31,33,21,24,27,5,17,18,16,11,3,10,4,15,2,13,7,1,12,6,8,19,14,9,0}, + {38,34,25,37,24,33,31,36,28,23,35,39,26,32,30,29,21,20,27,22,14,8,16,0,10,2,12,9,7,19,18,5,3,17,13,1,4,11,15,6}, + {26,28,21,36,34,31,30,39,20,38,27,32,22,35,37,23,25,29,24,33,0,10,2,7,1,4,14,8,16,12,9,15,3,5,17,11,13,18,19,6}, + {37,34,21,32,23,28,31,25,30,38,26,24,29,22,35,33,36,20,39,27,1,0,19,8,11,5,10,7,14,3,17,4,2,15,6,9,16,12,13,18}, + {31,20,35,23,34,27,33,32,26,30,36,38,22,28,39,21,37,25,29,24,18,16,2,4,7,11,15,6,0,10,13,19,17,9,12,5,1,8,3,14}, + {35,28,27,21,30,37,39,31,25,24,26,32,36,38,33,22,34,29,23,20,2,17,15,8,6,3,4,9,1,19,16,13,18,12,10,0,11,7,5,14}, + {25,37,21,20,26,35,24,28,34,33,30,29,27,31,22,39,36,38,32,23,14,19,16,5,18,7,10,6,15,0,13,9,11,3,1,17,4,8,12,2}, + {27,32,31,25,20,28,33,22,24,21,39,37,26,36,29,23,30,38,35,34,19,7,18,1,11,3,12,17,4,8,15,9,10,5,2,0,13,16,6,14}, + {37,32,22,25,24,28,30,33,21,29,36,39,34,20,23,31,27,35,26,38,10,16,5,2,4,3,12,11,17,6,15,7,18,14,8,1,19,0,13,9}, + {39,31,27,24,29,35,25,21,23,28,30,37,32,34,26,20,36,38,22,33,19,10,18,8,2,12,11,13,7,15,14,6,4,1,0,17,5,9,16,3}, + {36,20,25,35,37,27,33,21,28,23,38,34,30,29,32,22,26,39,31,24,1,18,3,9,4,12,5,16,0,13,8,11,14,6,7,10,2,19,15,17}, + {26,35,25,21,30,32,20,34,37,27,29,22,36,38,28,23,31,33,24,39,7,1,19,0,3,2,14,9,11,10,4,6,5,8,16,18,15,17,13,12}, + {34,37,23,33,25,38,24,27,20,39,32,26,29,21,36,30,22,35,28,31,17,0,13,15,4,16,2,11,10,5,14,7,19,1,18,9,3,8,12,6}, + {36,38,24,26,29,31,25,22,34,33,35,21,30,27,23,39,28,32,37,20,8,4,1,15,0,11,19,12,17,3,16,9,6,10,7,18,2,14,13,5}, + {37,27,24,31,29,36,33,39,20,22,21,35,23,28,26,30,25,38,34,32,1,11,18,0,5,13,15,4,16,9,3,6,10,14,2,12,7,17,19,8}, + {26,25,21,37,34,27,29,23,38,28,22,36,39,20,30,35,33,32,24,31,3,13,12,14,8,1,17,18,9,2,11,16,6,10,19,4,15,0,5,7}, + {22,26,24,23,30,21,25,35,37,29,38,36,27,32,34,39,33,28,31,20,3,8,14,0,16,18,10,9,6,5,11,17,13,15,12,7,19,1,4,2}, + {25,22,27,37,24,32,20,36,30,28,21,31,39,38,33,26,29,23,35,34,13,2,18,17,8,0,4,14,11,9,12,3,5,19,10,1,7,16,15,6}, + {22,26,29,32,36,28,38,34,24,35,23,31,37,39,33,30,21,25,27,20,5,4,1,8,11,2,9,18,10,15,13,12,19,7,0,16,3,17,6,14}, + {20,26,29,34,32,27,37,28,35,30,38,33,39,24,36,23,25,22,31,21,18,10,16,8,3,5,17,15,6,0,2,13,1,7,4,14,11,19,9,12}, + {33,20,28,34,39,29,32,26,38,23,22,35,24,37,36,31,30,27,25,21,17,19,2,7,4,6,9,13,0,16,1,11,10,14,8,15,18,12,3,5}, + {30,33,36,31,23,25,32,26,22,38,20,35,21,34,24,27,29,37,28,39,8,0,14,2,7,9,12,4,13,17,19,16,6,1,11,15,5,18,3,10}, + {37,22,20,32,35,26,30,25,38,31,39,23,21,36,29,28,33,27,24,34,1,6,15,19,10,14,5,9,11,7,17,16,13,12,0,4,3,8,2,18}, + {21,29,39,28,20,22,32,37,34,33,27,26,23,25,38,24,30,36,31,35,0,3,2,18,1,13,7,12,10,17,19,11,16,14,9,6,5,8,15,4}, + {26,30,38,20,39,22,29,33,32,36,24,27,37,35,31,25,28,34,23,21,5,14,9,8,18,10,6,13,16,3,12,4,1,15,2,7,11,19,0,17}, + {34,33,29,25,22,21,30,24,23,36,26,38,32,35,39,28,31,37,27,20,2,13,12,9,11,5,15,0,7,14,1,16,18,19,10,6,4,17,8,3}, + {31,36,35,24,21,23,29,26,39,27,38,25,22,34,37,33,32,28,20,30,7,19,16,0,8,6,1,4,17,15,5,18,12,11,13,2,9,14,10,3}, + {33,38,28,32,20,36,25,24,37,31,39,22,27,30,34,23,21,26,29,35,14,10,8,12,5,0,9,16,7,3,15,2,6,13,19,11,18,17,1,4}, + {31,38,24,20,29,23,39,37,21,30,34,25,32,28,27,36,35,26,33,22,19,11,2,6,17,15,7,12,1,5,8,0,9,4,18,3,14,16,10,13}, + {34,24,32,39,38,30,20,26,23,25,31,35,27,37,21,29,28,22,33,36,0,19,15,13,1,17,10,2,4,7,11,14,9,18,3,6,12,5,8,16}, + {29,25,23,31,28,26,30,36,33,20,34,22,37,24,27,21,35,39,32,38,18,11,17,5,1,10,12,7,14,4,2,9,8,13,19,3,6,0,16,15}, + {27,23,39,24,33,29,25,31,37,34,22,26,30,28,35,32,38,21,36,20,16,15,19,8,18,9,0,14,6,10,4,13,11,1,17,3,7,12,2,5}, + {39,23,34,22,37,21,28,30,36,26,29,31,33,32,24,38,25,20,35,27,0,5,7,9,14,4,12,16,18,13,15,2,10,17,11,6,8,1,3,19}, + {35,29,21,31,22,20,25,30,24,39,28,26,34,32,23,37,33,38,36,27,12,4,13,15,6,14,11,1,16,19,10,3,17,0,7,5,18,8,2,9}, + {23,30,20,34,39,36,31,26,32,25,38,37,24,35,21,33,28,27,29,22,18,16,8,17,11,19,12,0,9,13,15,6,5,1,3,2,14,7,10,4}, + {22,33,25,21,39,30,32,27,36,35,26,28,31,29,24,38,20,34,23,37,19,3,2,18,5,16,10,7,1,6,14,8,0,11,17,13,12,9,15,4}, + {35,39,24,29,37,28,26,20,25,31,22,33,27,34,23,38,36,30,32,21,18,3,10,1,6,8,17,2,4,9,14,16,11,5,0,19,12,15,7,13}, + {25,22,38,37,20,29,24,39,31,26,28,33,27,36,34,23,32,30,21,35,9,15,10,5,11,13,7,6,19,2,4,1,14,8,0,3,12,17,16,18}, + {31,37,27,20,30,33,26,21,34,38,25,35,24,39,29,23,28,36,32,22,16,3,5,10,14,1,15,2,19,12,4,6,18,7,13,9,17,0,11,8}, + {20,22,34,25,36,38,35,23,30,39,28,24,33,29,32,27,37,21,26,31,0,10,2,7,4,3,15,19,9,12,6,8,14,13,16,11,18,17,5,1}, + {20,28,33,36,23,38,26,29,35,24,22,34,30,32,27,25,39,37,21,31,8,18,15,1,9,12,4,19,5,10,17,0,16,11,7,14,3,13,6,2}, + {25,22,27,32,38,37,23,33,29,34,39,36,26,31,20,28,24,35,30,21,15,17,12,14,16,0,3,2,8,4,6,13,1,7,9,18,11,19,5,10}, + {20,33,36,24,21,29,35,25,28,34,30,39,26,31,23,32,38,27,22,37,17,19,8,6,13,16,2,1,15,3,11,7,12,4,18,10,9,14,5,0}, + {33,21,25,24,37,28,27,38,22,30,35,29,31,34,36,39,23,26,32,20,10,16,12,2,18,13,5,14,19,17,0,6,1,8,11,4,15,3,9,7}, + {31,26,37,33,36,22,32,38,25,39,35,23,28,24,29,21,30,27,34,20,14,13,18,19,7,9,1,5,11,4,17,0,6,3,15,2,12,10,8,16}, + {27,23,34,32,36,38,31,22,33,21,37,26,29,39,30,28,24,35,20,25,7,16,19,1,9,0,4,2,6,3,10,14,11,13,17,12,15,18,8,5}, + {29,27,31,23,26,20,37,32,39,30,33,21,25,35,24,34,22,36,28,38,6,15,12,10,2,9,5,16,17,7,4,18,11,8,14,0,3,13,1,19}, + {33,28,21,38,24,26,29,32,37,36,22,30,25,35,27,39,23,31,34,20,1,11,10,17,8,15,16,2,19,18,0,5,12,4,9,6,13,7,3,14}, + {21,33,22,36,35,27,24,31,34,39,28,32,38,23,25,29,26,37,30,20,9,4,2,8,7,0,14,6,19,11,5,12,17,15,13,18,1,3,10,16}, + {32,38,28,20,29,21,23,27,37,36,39,30,34,25,22,33,26,24,31,35,12,8,6,13,16,10,15,9,3,14,0,4,7,19,1,17,11,5,18,2}, + {23,38,27,36,39,37,29,20,22,30,32,34,24,21,33,25,28,31,26,35,18,12,8,10,3,15,9,2,16,7,6,13,17,19,0,11,14,5,1,4}, + {38,32,36,27,33,25,37,39,24,20,22,34,21,29,26,31,30,35,23,28,15,1,5,3,19,13,4,18,16,10,0,2,9,14,7,12,8,6,17,11}, + {27,29,23,24,20,38,36,22,35,30,39,34,28,37,26,31,21,33,32,25,9,18,6,12,0,10,16,5,3,13,1,4,7,15,14,8,19,2,11,17}, + {29,39,27,22,24,23,37,28,31,26,33,36,21,34,30,35,32,38,20,25,4,17,5,2,15,8,11,1,7,6,16,10,3,13,0,9,12,19,14,18}, + {36,30,33,21,34,31,26,38,24,22,27,25,20,35,39,23,37,29,32,28,12,3,19,15,13,9,6,7,11,17,1,14,18,16,4,8,0,2,5,10}, + {27,38,32,23,39,35,28,20,37,24,31,22,36,30,26,29,34,25,33,21,4,2,11,14,13,5,8,18,6,10,3,17,1,16,7,19,9,15,12,0}, + {28,37,24,34,26,22,38,32,30,21,33,36,31,29,39,23,25,27,35,20,2,19,11,3,17,7,16,4,14,5,10,8,1,12,6,0,15,9,18,13}, + {21,23,39,32,27,25,30,24,22,34,38,35,31,36,28,20,29,26,33,37,19,2,12,4,0,15,8,14,3,1,7,9,18,5,10,16,11,13,17,6}, + {31,25,21,24,37,22,38,36,20,27,26,34,30,33,29,23,39,32,28,35,17,5,13,16,3,9,15,4,7,10,18,11,14,1,6,12,0,8,19,2}, + {25,20,28,23,32,27,31,34,39,37,30,36,35,29,26,24,38,33,22,21,14,5,15,10,7,9,12,17,16,6,4,1,18,0,3,11,19,8,13,2}, + {24,38,23,22,21,35,25,29,28,20,37,32,34,30,26,36,33,31,27,39,19,0,4,10,15,11,3,14,12,9,1,5,17,13,6,2,8,16,7,18}, + {26,28,27,37,32,30,20,38,33,29,22,24,31,21,34,25,39,23,36,35,13,18,9,0,10,15,19,8,6,2,16,3,5,7,14,4,11,17,12,1}, + {27,37,33,38,31,34,23,29,26,32,36,21,35,24,30,22,39,20,25,28,5,17,14,2,19,4,15,9,13,11,18,10,7,0,6,12,16,1,3,8}, + {23,36,32,25,21,29,37,33,39,35,28,27,22,26,38,34,24,31,30,20,1,13,17,16,0,7,2,9,3,14,5,18,6,12,8,15,19,4,10,11}, + {20,31,26,37,36,32,28,35,39,23,27,25,29,22,38,34,24,33,30,21,17,19,15,5,8,2,11,1,0,13,12,3,18,10,7,16,14,4,9,6}, + {21,24,23,25,20,35,37,28,38,34,26,30,39,33,22,31,36,29,32,27,7,5,16,2,18,8,0,6,12,3,1,13,10,15,9,19,11,4,17,14}, + {39,30,38,21,36,23,20,24,28,22,25,29,27,31,35,32,34,26,37,33,19,16,5,13,2,10,15,8,18,1,9,17,3,0,12,7,4,6,11,14}, + {32,26,33,29,22,27,39,34,38,28,24,21,37,36,20,30,25,35,31,23,17,6,14,3,2,8,7,9,5,15,19,1,13,0,16,12,18,10,4,11}, + {38,36,31,27,37,30,34,22,20,24,28,26,23,21,33,35,29,39,25,32,19,4,10,16,5,8,11,2,7,12,15,3,17,1,6,9,14,18,0,13}, + {24,20,39,37,32,30,27,31,23,36,26,38,33,22,28,35,29,25,34,21,8,17,2,10,15,9,1,11,19,6,18,3,5,16,12,4,14,13,7,0}, + {32,37,34,26,21,38,22,25,23,30,35,24,29,31,36,28,39,27,33,20,19,7,11,0,10,8,12,1,16,18,2,6,15,14,9,4,13,17,5,3}, + {31,33,35,29,28,26,25,23,39,37,21,27,24,36,22,34,32,38,30,20,1,4,10,3,5,11,7,9,19,8,13,15,2,16,14,18,12,0,17,6}, + {29,34,28,36,25,23,26,38,33,22,27,37,20,24,39,32,31,21,35,30,11,13,1,14,10,15,9,5,8,16,6,18,3,2,17,7,12,0,4,19}, + {34,32,39,23,37,26,21,31,27,36,33,24,38,35,29,28,25,22,30,20,12,11,15,5,3,16,0,2,10,1,7,19,4,18,17,14,13,6,8,9}, + {30,35,23,33,38,31,27,37,20,34,32,24,36,26,39,22,25,29,28,21,11,16,10,4,0,9,3,12,15,17,6,8,13,5,1,14,18,7,19,2}, + {34,27,33,32,22,21,35,24,23,31,37,39,30,25,28,38,29,36,20,26,5,13,11,17,7,19,18,1,14,9,12,0,4,6,16,2,8,10,15,3}, + {31,34,37,35,32,27,20,24,23,38,26,33,22,28,30,25,29,39,36,21,10,12,5,17,1,13,8,11,9,0,4,3,16,19,15,6,18,2,14,7}, + {23,39,35,33,30,36,32,21,29,24,27,22,38,26,34,25,37,28,31,20,7,6,19,3,11,10,14,18,12,0,16,15,1,4,2,13,5,9,17,8}, + {38,24,36,34,31,22,37,29,20,28,26,30,32,21,33,35,23,25,39,27,6,9,3,16,2,8,10,15,18,5,1,14,13,4,7,17,11,19,12,0}, + {28,31,23,22,35,34,36,32,37,24,26,33,27,30,25,29,38,21,39,20,10,18,6,1,15,12,2,9,16,8,5,3,0,4,13,11,14,17,19,7}, + {37,22,21,34,23,29,31,27,30,32,25,24,33,39,28,20,35,26,38,36,3,6,15,5,1,17,13,19,9,2,10,18,14,11,4,8,12,7,16,0}, + {26,28,20,39,33,23,27,34,36,29,24,32,21,30,38,25,37,35,31,22,2,4,13,18,10,6,0,15,1,8,9,12,3,5,7,11,16,19,14,17}, + } + }, + { 41, { + {40,21,37,36,39,33,38,35,26,34,31,27,32,28,24,30,29,23,22,25,3,8,14,6,12,11,13,17,0,5,15,10,2,19,18,4,16,7,9,1,20}, + {26,36,28,27,23,33,40,38,35,30,25,32,31,29,34,24,21,37,39,22,19,20,1,15,18,4,7,5,16,8,17,3,10,2,13,6,9,12,11,0,14}, + {34,38,21,29,20,33,27,25,30,26,40,39,32,23,36,35,28,37,22,24,31,19,18,16,13,1,10,8,3,9,0,15,11,14,5,4,12,7,6,17,2}, + {26,40,36,39,25,35,28,24,32,34,20,31,27,30,22,38,23,29,33,37,21,12,8,19,1,17,3,9,6,0,13,7,2,15,5,4,10,16,14,18,11}, + {39,30,22,25,28,26,31,29,24,33,27,35,38,37,36,32,34,21,23,40,2,8,5,17,11,1,16,7,12,6,15,13,19,18,3,10,20,0,14,9,4}, + {29,37,20,24,36,22,26,34,30,39,32,25,31,35,38,28,27,40,23,33,21,19,8,6,11,13,18,17,5,2,7,12,15,10,9,4,14,1,3,16,0}, + {32,29,27,22,35,28,24,37,39,33,26,40,38,30,25,36,31,21,34,23,0,11,18,20,7,2,19,16,6,8,12,1,3,15,9,13,5,10,17,14,4}, + {34,40,27,36,28,25,32,38,33,39,22,35,23,26,24,29,20,31,21,30,37,5,0,2,1,17,16,3,14,8,18,6,11,19,9,13,12,10,7,15,4}, + {33,27,22,37,26,38,32,31,21,34,30,25,24,35,29,23,36,28,39,20,40,8,2,17,3,19,6,0,11,7,1,5,9,14,4,18,13,12,10,16,15}, + {24,30,38,25,33,35,31,28,37,27,39,23,26,32,36,21,34,29,22,40,13,7,1,6,12,15,5,4,19,3,17,9,8,0,2,14,11,20,16,10,18}, + {40,36,23,22,25,31,30,28,37,29,27,26,33,38,34,32,35,20,39,24,21,5,3,8,11,14,16,19,2,4,15,17,12,7,6,10,13,0,18,1,9}, + {21,24,26,37,34,40,22,31,35,32,39,29,27,33,36,38,28,25,23,30,5,3,17,15,1,6,8,16,20,12,0,11,7,13,2,19,10,9,18,4,14}, + {21,23,36,29,40,33,37,32,35,28,34,39,24,22,27,25,31,30,38,26,19,2,4,11,15,20,12,14,1,9,0,18,13,5,17,7,10,3,16,8,6}, + {33,40,35,25,21,26,28,36,31,39,30,34,27,23,24,20,32,38,29,37,22,18,6,2,14,3,5,0,15,8,10,7,16,12,11,13,4,9,17,19,1}, + {21,38,31,39,36,40,37,34,23,28,22,24,32,26,30,29,27,25,33,35,13,10,20,6,8,16,19,18,2,14,11,15,1,17,3,7,0,12,5,4,9}, + {21,20,30,39,35,33,40,28,25,24,38,27,31,23,29,32,26,37,34,36,22,11,12,17,1,7,10,18,5,14,3,0,9,4,2,8,16,6,13,15,19}, + {22,38,36,35,25,30,27,21,24,26,32,29,39,23,28,40,33,34,37,31,11,19,0,14,17,8,12,6,5,3,20,7,10,1,4,16,15,9,13,18,2}, + {34,24,35,28,36,30,39,29,37,40,25,23,21,33,32,38,27,31,20,22,26,13,5,14,0,12,19,17,10,9,3,2,16,8,15,6,11,18,4,1,7}, + {38,29,33,30,24,39,32,22,26,37,28,36,23,34,20,35,27,25,40,31,21,18,8,2,7,10,14,5,13,0,9,15,11,3,17,4,16,12,19,1,6}, + {28,36,21,38,37,25,34,40,35,26,22,30,33,27,24,32,39,31,23,29,0,17,16,4,8,20,15,3,5,13,11,1,9,12,19,14,2,7,10,6,18}, + {32,36,31,30,34,27,26,33,21,23,38,29,40,28,24,37,22,39,25,35,7,4,18,2,1,17,16,11,20,3,9,15,8,14,6,19,12,0,5,10,13}, + {35,20,39,31,21,33,24,37,23,34,28,25,29,26,40,30,36,32,22,38,27,17,0,3,14,7,9,5,8,19,4,2,10,16,13,1,6,12,15,11,18}, + {24,35,28,23,31,37,34,30,38,20,27,33,26,29,25,40,32,39,36,22,21,13,5,10,6,17,3,7,1,18,9,2,0,11,19,15,14,8,4,16,12}, + {33,39,35,27,37,22,32,25,34,31,30,36,40,23,29,24,26,28,21,38,19,18,13,3,2,11,20,9,15,4,0,5,14,6,12,10,1,17,8,7,16}, + {37,25,31,21,39,23,34,27,32,22,24,33,35,29,26,38,40,36,28,30,8,5,14,2,10,9,15,4,1,0,13,7,11,6,18,3,19,20,12,17,16}, + {38,32,25,29,33,26,40,39,35,27,23,37,31,30,22,24,36,34,28,21,19,12,15,7,0,17,9,11,18,4,6,1,16,13,8,2,5,20,14,3,10}, + {40,26,38,23,39,35,37,25,28,34,22,27,24,36,33,30,32,31,29,21,18,3,13,19,7,14,10,15,11,5,0,12,4,2,8,17,1,16,6,20,9}, + {28,22,39,21,37,40,38,36,27,32,26,31,25,24,29,34,30,35,33,23,16,20,10,8,4,1,0,7,15,19,14,18,9,12,3,11,6,17,5,2,13}, + {36,40,25,31,26,23,22,29,27,37,21,39,32,30,33,24,38,35,34,28,11,10,19,15,8,7,2,17,14,4,0,16,12,9,13,3,6,5,1,18,20}, + {27,30,26,36,21,24,29,23,33,28,38,22,32,31,39,37,25,35,34,40,17,11,20,12,8,16,1,15,14,2,19,4,10,5,18,6,0,13,7,9,3}, + {27,25,28,34,36,21,37,32,23,31,24,20,29,26,30,35,38,33,40,39,22,10,6,1,8,2,15,9,12,3,13,11,18,17,5,0,19,4,7,14,16}, + {40,26,37,22,25,35,28,31,29,32,30,36,38,33,27,39,24,23,34,21,3,12,1,9,5,19,10,14,6,16,2,4,17,13,18,8,11,20,0,15,7}, + {40,25,31,29,34,24,35,33,22,26,30,23,32,38,27,36,28,37,21,39,19,9,13,4,11,1,3,6,12,2,0,15,20,17,7,18,14,5,8,10,16}, + {24,33,25,30,29,37,32,34,36,27,23,40,22,35,31,26,39,28,38,20,21,4,19,18,9,0,3,1,15,11,7,12,16,14,13,5,8,17,10,6,2}, + {24,20,32,28,38,29,39,37,40,23,33,30,27,25,22,36,21,34,31,35,26,0,17,8,10,2,1,3,4,11,14,13,18,6,15,9,5,7,12,16,19}, + {36,20,38,30,37,21,26,22,34,28,35,40,33,25,27,24,31,29,39,23,32,5,7,15,19,0,6,16,10,12,18,9,1,17,4,2,13,8,3,11,14}, + {34,37,22,32,27,29,28,21,30,36,33,26,20,35,39,31,40,25,23,24,38,11,1,3,18,16,5,8,4,6,0,17,15,19,12,9,14,2,7,10,13}, + {29,38,36,33,35,34,37,23,25,24,31,39,30,28,27,40,32,21,22,26,7,11,20,1,5,8,19,0,17,2,12,9,14,3,16,18,6,10,4,13,15}, + {23,33,39,24,21,40,30,27,36,22,20,29,38,32,28,34,37,31,35,25,26,16,2,0,10,6,8,7,18,11,5,12,19,4,3,14,17,1,9,13,15}, + {39,32,23,22,26,30,33,27,40,35,38,31,21,36,25,28,34,37,29,20,24,5,1,8,16,15,6,2,13,4,19,12,0,14,11,17,7,10,18,9,3}, + {21,22,26,23,28,27,40,33,37,39,38,30,36,32,29,24,35,34,31,25,14,17,2,20,11,10,7,12,3,9,8,18,6,5,1,15,13,19,16,4,0}, + {29,25,37,20,31,40,23,39,28,35,34,36,27,26,38,33,30,22,32,24,21,18,6,17,19,12,3,8,4,2,0,10,1,14,9,7,11,5,13,16,15}, + {26,29,23,28,22,32,39,25,36,35,24,33,27,34,37,30,21,40,38,31,19,7,17,3,5,18,11,15,4,14,13,6,0,9,2,8,1,12,10,20,16}, + {39,20,24,38,30,37,23,27,36,32,26,29,21,40,34,31,35,33,28,25,22,12,5,18,15,1,7,17,13,16,6,10,8,14,19,3,9,0,11,2,4}, + {22,38,29,21,24,39,32,30,36,40,23,25,27,34,37,26,35,31,33,28,3,17,20,14,4,11,1,12,19,18,15,0,16,2,13,5,7,10,8,6,9}, + {21,24,26,33,32,34,28,25,31,38,22,29,40,37,36,27,23,39,30,35,12,19,15,4,1,13,2,10,18,8,6,11,7,17,20,14,0,16,9,3,5}, + {38,25,37,33,28,31,26,30,32,34,22,20,27,40,24,29,36,39,35,21,23,12,19,2,4,14,5,3,7,0,10,17,13,6,9,18,16,15,8,11,1}, + {35,20,36,32,30,29,27,22,25,40,28,38,33,24,23,26,34,39,31,37,21,16,12,2,5,4,8,0,14,3,7,17,13,11,6,19,9,15,18,10,1}, + {22,33,23,34,38,31,21,25,37,39,26,29,36,24,30,32,20,27,40,35,28,19,8,3,12,16,9,15,5,18,13,0,6,2,1,14,10,4,17,7,11}, + {28,37,34,22,24,26,29,40,23,35,30,33,25,38,27,36,39,32,21,31,6,17,2,8,11,5,13,1,16,19,14,15,4,7,12,9,18,20,10,0,3}, + {40,34,27,36,39,29,38,22,25,31,26,23,28,32,20,35,30,21,37,24,33,5,8,1,9,16,15,4,2,13,17,0,19,6,12,3,11,7,14,10,18}, + {35,20,27,29,34,22,40,25,30,38,23,36,39,28,31,33,24,32,26,37,21,16,6,7,0,9,14,8,13,4,18,17,19,10,5,2,1,12,11,3,15}, + {29,26,21,33,25,34,28,30,39,35,27,31,36,32,40,24,23,38,22,37,10,19,12,1,11,4,14,7,13,0,9,3,5,17,18,2,6,15,20,8,16}, + {27,23,35,28,34,37,22,29,31,33,36,32,24,30,39,26,20,25,38,40,21,6,1,5,9,15,14,13,18,2,19,3,11,0,16,7,4,8,17,10,12}, + {27,39,33,23,36,38,25,21,30,24,32,34,29,22,37,28,31,40,26,35,3,7,14,12,16,4,20,10,15,5,8,2,9,18,11,0,6,13,19,1,17}, + {36,39,21,34,27,22,31,20,25,35,38,29,37,26,24,40,33,28,23,32,30,3,11,10,12,19,0,16,14,13,9,17,5,7,15,18,1,6,2,8,4}, + {39,36,34,25,31,23,40,29,35,30,24,22,37,21,38,33,28,26,20,27,32,3,14,12,15,19,4,7,5,2,10,8,0,11,13,17,6,1,9,18,16}, + {38,25,23,32,26,30,21,37,29,33,36,35,28,34,22,31,24,40,27,39,15,19,9,17,16,1,13,18,14,2,20,4,7,6,11,0,12,3,8,5,10}, + {28,37,39,38,34,27,26,25,33,29,36,30,22,31,35,23,32,24,40,21,5,20,4,0,15,7,9,17,14,19,3,8,11,6,1,13,16,2,18,12,10}, + {22,38,40,23,25,36,24,32,26,28,30,39,27,31,35,34,33,37,29,21,4,12,16,2,18,8,11,5,17,9,1,10,19,6,20,3,14,0,15,7,13}, + {32,27,35,21,28,26,22,37,39,30,24,23,29,40,31,34,33,25,38,36,19,9,6,17,10,18,4,12,0,13,1,3,15,11,7,14,16,20,5,8,2}, + {37,23,29,36,31,40,25,21,22,34,38,35,27,32,39,30,26,33,28,24,11,20,0,9,5,13,17,1,16,8,15,2,19,4,10,6,12,18,7,3,14}, + {28,20,40,39,31,37,35,22,25,29,26,38,23,30,33,27,34,24,32,21,36,17,9,3,15,0,2,14,8,16,18,13,6,11,7,5,19,4,10,12,1}, + {29,34,37,25,21,27,33,38,24,30,39,36,26,23,28,35,32,40,22,31,11,15,0,9,19,3,12,1,10,20,13,2,4,8,17,16,6,18,7,14,5}, + {21,34,31,29,24,22,25,37,40,30,26,39,33,23,27,35,28,38,20,32,36,5,2,6,9,8,0,10,4,11,19,18,16,14,7,13,1,15,12,3,17}, + {32,34,27,37,36,38,40,20,33,39,26,22,25,23,29,28,24,30,35,31,21,9,4,6,0,15,1,5,8,14,19,16,13,2,10,17,7,11,18,3,12}, + {33,31,20,28,26,21,37,36,40,25,30,27,29,39,24,22,35,34,38,32,23,19,12,6,14,7,18,9,16,15,0,4,10,5,13,8,3,17,11,2,1}, + {24,35,25,22,29,26,36,21,31,33,32,34,39,27,37,30,38,40,28,20,23,12,4,6,5,14,7,9,1,16,18,13,19,8,17,10,0,11,3,15,2}, + {33,20,25,35,22,36,29,26,24,28,39,30,32,34,40,21,38,31,37,27,23,11,4,9,0,8,18,1,6,19,12,16,15,14,17,2,7,5,10,3,13}, + {35,20,38,34,26,28,39,32,30,22,33,37,40,23,25,27,29,36,31,21,24,6,3,13,1,8,4,5,15,7,18,14,16,19,9,0,17,11,2,10,12}, + {36,30,39,24,28,37,34,26,40,27,21,33,38,35,29,32,25,31,23,22,1,8,2,14,16,3,6,18,17,5,4,15,0,13,7,11,10,12,9,20,19}, + {31,21,33,36,26,39,34,22,23,29,35,25,27,40,37,32,38,30,28,24,7,3,5,12,11,6,10,8,13,20,17,16,15,19,14,4,1,9,0,2,18}, + {32,26,23,40,36,31,28,27,25,21,24,39,35,30,38,33,37,22,34,29,3,9,7,5,8,19,16,12,17,1,13,15,0,2,18,14,4,11,6,10,20}, + {24,36,31,28,21,38,37,32,29,25,39,23,40,26,34,30,22,35,33,27,19,3,5,9,8,16,14,12,7,15,1,18,11,4,2,0,20,17,13,10,6}, + {27,39,22,33,30,36,26,34,29,24,32,35,37,31,20,23,25,28,38,40,21,0,2,5,12,3,10,1,16,19,13,11,6,14,4,8,18,15,9,7,17}, + {23,21,40,31,33,29,37,27,25,35,26,24,36,39,32,34,38,28,30,22,4,14,6,20,3,16,9,2,10,17,7,1,13,8,0,18,12,15,5,11,19}, + {35,27,32,25,40,33,37,38,28,39,30,22,36,21,29,24,31,23,26,34,11,19,1,6,2,13,10,18,15,7,14,16,8,5,4,0,20,9,3,17,12}, + {24,30,26,36,22,31,25,40,34,23,38,37,33,39,35,28,32,27,29,21,3,8,16,9,12,18,10,7,2,11,17,19,15,0,4,13,6,14,20,5,1}, + {31,37,24,21,38,26,39,32,34,30,29,33,35,22,36,27,23,28,25,40,12,20,14,3,17,0,8,15,5,11,10,9,6,13,16,4,2,18,7,1,19}, + {26,31,36,35,28,33,32,23,39,29,22,27,38,25,40,34,24,37,20,30,21,15,19,17,16,12,2,18,11,8,1,14,9,7,13,0,4,6,3,10,5}, + {22,26,34,23,27,29,25,33,38,24,37,20,31,39,35,32,40,36,21,28,30,1,15,6,11,3,2,4,12,10,0,19,5,7,16,14,17,13,18,8,9}, + {21,32,40,38,20,22,30,35,28,39,31,29,26,33,24,37,34,27,36,25,23,3,19,9,6,4,12,18,5,1,13,11,8,17,16,7,15,10,14,0,2}, + {25,31,29,37,21,28,24,32,35,20,38,26,30,27,34,23,39,36,40,33,22,3,1,15,5,8,11,13,7,12,2,10,17,14,4,0,9,19,6,16,18}, + {40,36,21,39,31,20,35,26,30,29,28,22,24,34,25,23,33,27,38,32,37,18,5,1,12,17,15,6,10,14,13,4,19,16,2,9,8,7,11,3,0}, + {31,33,22,37,21,28,26,39,29,27,25,32,23,36,30,38,20,40,24,35,34,14,19,16,11,7,2,4,18,15,3,17,1,12,10,8,0,13,6,9,5}, + {28,30,34,29,37,32,36,38,24,27,26,23,21,35,33,40,25,22,39,31,0,20,1,6,8,15,11,3,16,12,7,4,9,2,14,18,13,17,19,10,5}, + {27,24,35,21,34,30,28,39,37,25,32,38,33,29,36,22,40,31,23,26,15,20,1,9,19,7,13,17,5,3,8,14,12,6,10,2,18,16,0,11,4}, + {39,31,21,26,36,34,25,38,30,29,23,37,40,32,22,35,24,28,20,33,27,9,1,12,14,11,16,13,17,6,19,2,4,3,0,7,18,8,15,5,10}, + {21,24,37,31,26,25,30,39,33,28,20,35,38,34,29,27,36,23,40,32,22,18,15,6,0,17,16,9,13,5,19,10,3,11,1,8,7,14,12,4,2}, + {38,26,22,36,34,23,25,39,28,35,37,31,24,33,21,40,32,29,27,30,5,2,6,16,7,19,1,13,15,8,0,4,10,12,14,9,18,20,17,3,11}, + {32,27,38,30,39,31,35,26,40,34,36,25,22,20,28,21,29,33,37,24,23,7,5,10,1,3,17,2,11,8,16,18,14,12,19,6,0,15,13,4,9}, + {36,35,38,34,30,26,23,31,24,27,25,32,21,39,33,40,29,22,28,37,4,19,1,15,0,12,10,14,17,16,7,6,5,3,11,13,2,9,18,20,8}, + {25,24,35,27,40,26,37,22,33,21,30,39,36,28,34,31,38,29,32,23,18,13,1,6,19,9,15,10,0,4,11,7,16,2,20,5,17,8,14,3,12}, + {23,29,24,27,33,40,28,39,35,22,32,34,31,21,26,37,30,36,25,38,2,5,14,11,8,13,6,1,19,15,12,7,9,4,16,18,10,0,17,3,20}, + {32,26,31,27,40,38,37,33,39,28,34,25,21,36,23,35,30,20,24,29,22,4,19,0,5,1,9,14,12,6,18,13,16,8,3,15,11,7,2,17,10}, + {27,23,20,36,26,37,34,40,25,35,38,29,32,30,28,24,31,39,33,22,21,11,5,10,4,17,14,7,1,13,16,8,18,15,6,3,0,2,12,19,9}, + {28,31,26,32,38,22,25,21,30,39,27,35,40,33,24,20,37,36,23,29,34,16,10,8,1,6,9,4,3,0,12,14,7,5,13,19,17,15,11,2,18}, + {33,28,26,31,23,32,25,21,35,24,29,27,37,40,30,36,34,39,38,22,7,5,2,8,9,0,17,4,14,13,1,3,20,12,16,19,15,6,18,11,10}, + {27,33,24,37,31,29,20,23,35,40,38,28,32,39,30,25,21,26,34,22,36,14,5,8,2,1,12,11,4,0,15,17,13,18,10,3,19,6,16,7,9}, + {35,32,30,28,24,21,31,27,39,25,37,23,38,20,34,40,26,36,33,22,29,19,12,9,7,16,11,2,18,1,13,6,8,14,3,5,0,10,4,15,17}, + } + }, + { 42, { + {41,22,38,37,24,40,27,33,32,39,36,29,28,35,25,21,34,31,30,26,23,17,5,8,2,6,19,9,0,11,18,15,7,20,10,13,4,3,12,1,14,16}, + {38,24,39,27,33,29,28,31,34,25,23,41,26,22,35,30,36,40,37,21,32,20,7,15,5,8,10,9,14,0,11,4,3,18,13,16,12,1,17,19,6,2}, + {28,37,34,26,38,27,33,30,31,29,39,32,25,41,23,40,36,21,35,22,24,17,6,5,3,12,0,14,18,8,11,16,7,19,2,4,9,1,13,10,15,20}, + {25,38,33,36,41,28,21,24,31,29,26,37,27,23,34,32,39,35,40,30,22,18,0,10,14,11,20,19,3,5,7,12,4,15,8,13,9,2,16,6,1,17}, + {32,29,28,27,39,22,30,36,34,41,26,31,33,21,35,38,40,23,25,24,37,16,12,8,0,13,19,10,17,1,6,18,9,7,4,3,5,15,14,20,11,2}, + {26,23,39,37,30,34,41,32,36,33,28,35,38,27,31,29,25,40,22,21,24,20,2,7,14,13,9,11,15,6,16,18,0,3,19,17,8,5,4,10,12,1}, + {29,39,34,21,30,22,25,31,36,41,37,33,40,27,32,28,24,38,35,23,26,3,16,9,4,8,5,11,0,19,17,6,1,13,20,2,14,10,12,15,7,18}, + {32,29,33,28,21,36,23,37,40,26,22,24,38,35,25,30,34,31,41,27,39,19,4,6,11,16,3,8,17,14,10,9,15,5,12,7,18,13,20,2,1,0}, + {32,30,29,26,37,36,31,34,39,25,33,35,41,21,27,22,40,28,23,38,24,13,17,1,18,9,16,2,7,19,4,6,0,11,15,10,20,12,14,5,3,8}, + {38,33,35,34,39,31,40,28,23,22,24,27,29,41,25,36,26,30,32,37,21,16,3,0,7,5,12,14,13,1,11,10,18,20,2,6,4,15,19,8,17,9}, + {23,33,28,41,21,34,36,37,29,38,27,26,25,39,22,31,24,30,35,40,32,20,9,14,5,16,18,17,3,6,1,10,12,0,13,11,19,7,4,15,8,2}, + {36,25,34,30,28,39,41,38,27,22,40,33,29,31,37,26,21,23,35,32,24,20,16,14,2,19,12,6,1,13,0,18,15,4,3,8,7,9,5,17,11,10}, + {36,23,38,40,28,27,32,22,25,31,39,30,35,33,41,24,37,29,26,34,21,14,10,6,8,20,2,1,9,3,12,15,5,0,4,18,16,7,19,13,11,17}, + {21,33,30,38,34,29,36,32,39,37,31,24,40,28,22,25,41,23,26,35,27,19,16,20,3,2,14,1,17,7,11,0,13,15,9,4,8,12,18,6,5,10}, + {21,25,31,40,30,22,34,37,26,24,27,41,36,33,29,32,39,35,28,23,38,20,8,14,7,9,4,18,17,1,0,19,11,3,13,5,16,10,2,12,6,15}, + {31,30,36,26,34,24,38,37,33,29,23,21,27,25,35,41,32,40,39,22,28,6,0,8,7,5,16,10,20,19,9,3,13,18,17,15,4,12,11,1,14,2}, + {37,34,29,28,40,33,32,39,26,21,41,38,24,35,23,36,25,30,27,22,31,2,0,7,6,9,1,12,15,8,17,14,20,5,19,10,13,3,18,11,16,4}, + {37,28,35,33,27,34,32,40,30,26,41,21,29,38,24,36,22,31,39,25,23,11,0,2,14,6,9,12,16,4,8,20,5,3,19,1,15,7,13,18,10,17}, + {38,22,39,33,30,34,41,32,36,28,40,37,25,23,35,21,27,31,29,24,26,19,9,5,16,3,12,15,7,0,8,18,14,17,11,10,2,20,4,6,1,13}, + {35,39,37,34,40,27,36,26,24,29,28,31,25,23,30,33,21,41,22,32,38,9,12,16,1,5,0,13,11,3,7,14,2,17,15,19,8,4,18,10,20,6}, + {39,27,38,41,24,31,34,37,29,28,30,21,36,35,23,26,32,25,40,22,33,10,19,3,6,2,4,17,1,18,14,8,7,20,15,5,13,16,0,12,11,9}, + {38,23,28,41,37,30,35,33,34,40,24,26,25,36,27,32,39,21,29,22,31,14,8,17,0,7,10,2,19,16,13,9,15,18,1,12,6,11,4,3,5,20}, + {41,25,39,36,23,38,37,40,32,35,34,30,22,21,33,26,31,24,28,27,29,15,11,13,9,14,12,8,4,18,2,6,5,19,20,1,7,17,3,10,0,16}, + {41,30,27,40,26,33,21,24,28,36,35,37,29,23,38,34,39,32,31,22,25,14,20,7,1,9,18,12,16,13,6,5,17,0,11,19,8,4,2,15,3,10}, + {21,37,32,24,33,36,25,35,27,23,31,39,30,22,34,40,26,38,29,41,28,6,10,8,1,20,5,18,17,2,7,9,15,12,11,13,16,0,14,3,19,4}, + {25,38,36,30,22,26,24,35,28,40,34,23,31,33,29,21,41,37,39,27,32,4,6,11,16,14,13,12,17,7,9,19,15,3,10,1,8,2,5,18,20,0}, + {38,28,32,29,31,22,41,27,24,33,26,39,23,37,34,36,35,21,25,30,40,20,1,18,11,9,3,2,5,15,0,19,10,16,6,4,14,12,7,13,17,8}, + {31,34,28,35,37,39,32,24,33,41,29,40,26,38,27,22,21,25,30,36,23,3,15,20,7,6,12,14,2,10,18,13,17,8,9,0,19,4,16,5,11,1}, + {34,24,31,37,21,23,26,30,40,35,29,39,25,36,22,41,28,32,38,33,27,6,20,9,2,17,12,0,10,3,7,13,4,14,19,11,18,16,8,5,15,1}, + {28,25,27,30,40,26,24,39,32,36,31,38,41,34,29,33,35,22,37,23,21,20,13,8,11,9,5,0,19,17,2,6,1,7,15,14,16,18,10,3,12,4}, + {29,26,32,40,38,36,24,28,33,25,41,23,34,30,22,35,31,27,37,39,21,4,8,12,10,0,7,13,18,1,15,3,9,20,14,6,17,11,2,5,16,19}, + {36,25,28,37,27,24,38,41,32,30,26,23,35,40,34,29,39,31,33,22,21,20,18,11,1,19,6,14,4,16,13,10,5,9,3,15,7,0,8,2,12,17}, + {36,39,35,24,29,25,34,38,22,33,28,40,31,37,41,26,30,21,32,27,23,15,20,13,1,7,10,19,8,18,3,16,5,17,14,9,12,6,0,11,4,2}, + {31,21,35,40,36,23,37,27,29,28,38,33,25,30,34,39,24,41,26,32,22,20,0,14,12,17,9,5,7,15,4,11,10,1,3,16,19,13,6,8,2,18}, + {24,22,28,27,25,30,36,31,33,38,29,35,34,37,40,26,23,41,21,32,39,7,2,20,19,6,10,13,8,14,1,3,16,18,5,15,0,11,17,4,9,12}, + {32,24,36,29,39,21,40,26,23,30,28,41,37,35,33,25,38,31,34,27,22,11,17,9,2,12,7,18,15,6,4,10,8,16,1,5,13,0,3,14,20,19}, + {25,27,40,32,29,28,39,33,38,31,26,23,37,34,22,36,35,24,30,41,21,20,7,2,14,8,12,4,6,18,9,11,19,17,5,16,10,13,0,3,1,15}, + {26,40,37,22,33,41,27,31,30,28,39,35,34,24,29,36,25,32,23,38,21,1,14,7,11,0,13,8,6,5,18,2,9,3,15,19,10,12,16,20,4,17}, + {32,26,39,24,33,35,29,22,30,41,23,31,38,34,36,40,28,25,27,37,21,3,18,10,13,7,5,1,12,6,4,14,8,15,9,17,19,2,0,11,16,20}, + {22,27,26,30,21,24,35,29,23,38,25,28,36,31,41,34,40,37,32,39,33,20,14,19,16,17,2,1,15,11,5,13,18,10,7,6,8,0,4,12,3,9}, + {39,23,27,31,40,28,37,33,35,22,34,41,32,30,25,38,26,36,29,24,21,19,17,11,5,15,20,3,8,12,18,16,0,9,13,4,14,2,1,7,6,10}, + {35,38,37,33,29,32,34,24,22,26,39,41,27,21,30,36,40,23,31,25,28,2,15,19,11,10,4,0,17,13,6,16,9,8,18,5,14,7,12,1,20,3}, + {37,35,27,40,30,36,22,28,38,24,41,33,21,32,26,25,31,29,39,34,23,5,16,10,1,19,14,2,13,17,18,8,7,20,11,0,4,9,15,12,6,3}, + {41,40,34,25,35,21,31,39,32,36,30,33,27,37,24,38,28,22,26,23,29,3,17,6,13,19,10,14,18,2,12,1,8,0,11,15,4,20,5,16,9,7}, + {25,36,30,37,41,21,33,39,23,24,29,31,35,40,28,22,26,32,34,38,27,15,3,10,9,11,16,2,14,18,6,17,0,20,8,1,5,13,19,4,12,7}, + {29,35,30,39,33,21,25,22,37,23,28,26,32,31,40,36,38,24,27,34,41,19,3,15,6,18,0,11,4,7,14,1,9,13,10,5,2,12,20,16,8,17}, + {25,23,39,36,30,21,28,33,22,26,32,29,31,37,34,40,38,41,27,35,24,10,2,20,7,16,3,14,6,12,9,13,18,15,5,17,0,4,8,11,1,19}, + {22,26,21,27,37,25,24,39,29,40,28,36,30,34,31,23,38,35,33,32,41,3,7,8,6,12,9,4,17,15,10,14,19,18,13,5,1,16,2,0,11,20}, + {36,23,26,25,31,21,37,28,38,29,41,33,24,35,40,32,34,27,30,22,39,15,11,17,6,2,7,1,9,13,8,14,5,19,16,3,12,20,0,18,4,10}, + {40,28,41,23,33,32,26,22,39,27,36,25,31,34,38,30,29,21,37,35,24,16,15,14,1,20,3,18,13,5,11,6,0,4,17,19,2,12,9,8,10,7}, + {24,35,37,33,27,36,30,28,26,40,23,32,21,31,34,41,39,38,22,29,25,15,5,4,18,16,19,8,12,10,3,1,9,7,17,14,6,20,13,2,11,0}, + {26,32,31,40,25,38,24,35,33,41,29,39,34,23,37,22,21,36,27,30,28,3,12,14,20,18,17,10,16,15,1,6,11,8,5,7,9,19,13,0,2,4}, + {24,26,33,31,27,32,40,34,21,25,41,28,38,29,35,39,37,36,22,30,23,8,1,20,2,4,6,18,14,9,12,3,10,19,15,13,7,11,17,0,16,5}, + {26,32,22,21,23,29,39,35,25,30,27,33,37,41,28,31,36,24,40,34,38,16,20,10,1,9,7,15,0,4,8,13,12,14,3,5,17,11,19,2,18,6}, + {39,41,31,29,21,35,28,40,24,30,37,25,32,23,38,33,27,36,22,26,34,20,18,2,8,11,1,16,14,10,12,15,9,13,6,4,17,7,5,0,3,19}, + {23,27,22,39,25,31,26,33,41,28,38,37,24,40,34,32,21,36,29,35,30,0,15,17,10,14,13,20,7,18,8,9,1,16,3,11,5,4,19,12,2,6}, + {34,31,30,29,22,27,25,40,37,24,36,33,35,28,39,26,23,32,41,38,21,18,5,2,9,7,0,6,13,1,14,17,19,4,12,3,10,20,8,16,11,15}, + {38,28,22,30,37,41,32,21,36,26,31,33,39,35,29,24,27,23,25,40,34,20,18,11,13,0,4,6,16,9,7,14,17,8,19,12,5,2,10,3,15,1}, + {26,36,27,30,40,37,21,39,35,38,41,28,22,29,34,32,23,25,33,24,31,19,12,15,10,20,14,6,0,11,1,9,3,5,7,17,8,18,4,2,13,16}, + {24,35,26,21,33,22,31,41,37,40,36,39,38,27,30,34,23,25,29,32,28,20,13,19,6,10,14,16,4,0,12,9,11,1,17,7,3,15,8,5,18,2}, + {32,25,37,26,28,30,36,31,35,34,33,27,23,21,39,41,22,29,40,38,24,15,14,1,6,0,2,4,18,9,11,3,5,7,19,16,12,17,13,20,10,8}, + {36,29,38,28,30,24,37,32,22,35,33,23,27,25,40,39,41,26,31,34,21,13,1,10,18,15,14,2,6,5,4,12,3,11,9,7,0,19,16,8,17,20}, + {21,41,25,38,32,37,29,27,23,22,35,39,28,36,26,33,30,40,24,34,31,0,14,8,13,12,20,7,17,6,16,9,3,15,19,10,4,2,11,18,5,1}, + {25,22,35,38,23,30,41,31,21,28,39,37,32,27,40,29,26,36,34,24,33,13,17,6,9,0,7,11,3,1,16,4,14,5,20,19,8,18,15,2,12,10}, + {35,28,32,26,24,30,38,21,40,23,37,33,25,34,36,41,29,27,31,39,22,18,7,11,17,19,6,15,12,1,16,5,10,8,13,0,9,4,3,14,2,20}, + {29,37,28,34,24,30,32,39,33,38,23,26,40,25,35,41,22,27,31,36,21,5,8,17,14,6,19,12,20,0,11,18,4,9,1,16,7,3,15,13,10,2}, + {26,37,39,30,32,29,28,33,41,25,31,40,27,23,34,22,24,35,38,36,21,17,12,9,0,18,4,1,16,7,10,6,20,15,13,11,8,14,19,5,3,2}, + {28,34,41,30,33,31,38,21,23,26,37,22,25,40,36,27,39,29,24,35,32,14,18,0,5,11,17,9,13,15,7,1,3,16,2,8,10,6,20,12,4,19}, + {26,30,32,22,41,24,37,40,21,28,33,29,39,23,38,25,31,34,27,35,36,14,1,8,18,7,20,2,12,6,16,13,11,5,10,0,15,9,3,19,4,17}, + {25,40,36,23,27,41,22,29,24,26,31,28,32,38,37,34,21,35,30,33,39,20,19,12,2,4,18,5,11,7,9,8,16,6,15,3,13,10,0,14,1,17}, + {28,30,21,25,36,27,24,26,34,29,35,39,37,33,41,40,32,38,23,31,22,13,12,0,6,9,2,20,8,10,16,18,1,4,17,11,5,19,15,3,14,7}, + {33,41,37,34,24,40,35,31,36,21,29,28,27,38,30,25,32,23,39,26,22,20,6,17,5,14,13,2,16,9,11,3,12,8,4,19,1,0,7,18,10,15}, + {23,29,40,26,34,30,27,33,35,39,37,41,25,38,32,24,28,36,22,21,31,10,1,16,18,13,7,5,19,11,8,20,12,0,4,3,9,17,15,6,14,2}, + {39,24,37,31,30,38,26,29,22,34,40,23,28,21,27,25,32,36,33,41,35,15,2,17,8,11,7,10,0,3,6,5,13,4,18,16,9,19,12,1,20,14}, + {30,29,38,34,22,21,41,28,40,27,31,35,25,24,23,33,39,36,32,37,26,8,7,0,6,5,3,14,20,18,17,10,9,11,19,16,1,15,13,4,2,12}, + {32,39,41,25,30,27,34,22,26,23,28,36,24,35,37,33,40,29,31,21,38,6,12,9,8,10,19,4,7,5,0,18,16,1,17,3,20,14,11,13,2,15}, + {39,34,40,24,26,38,30,25,37,22,33,21,27,23,31,28,36,32,29,41,35,11,2,13,18,12,14,17,4,9,0,20,7,1,10,6,16,8,3,15,5,19}, + {35,41,25,31,38,22,29,36,34,27,37,40,33,26,28,39,24,30,23,32,21,8,10,4,2,16,14,9,7,0,12,1,18,6,15,11,5,19,13,17,20,3}, + {31,25,22,40,23,21,24,37,26,39,33,29,28,41,34,36,32,35,27,38,30,20,14,12,18,9,6,17,19,0,11,4,2,7,16,3,15,8,1,13,10,5}, + {27,24,37,29,39,31,30,23,35,41,33,40,26,25,32,36,28,38,34,22,21,2,8,5,15,6,3,1,12,9,4,18,10,20,13,7,17,11,14,16,0,19}, + {31,35,33,38,37,24,27,36,28,32,30,40,34,26,22,39,23,21,29,41,25,20,15,9,1,3,5,8,18,2,7,13,16,10,0,6,4,11,17,14,12,19}, + {34,23,22,37,26,41,30,27,39,35,40,24,21,38,28,25,33,36,29,32,31,0,12,5,13,15,10,17,8,4,6,9,1,7,14,19,2,20,11,3,18,16}, + {29,27,41,26,32,24,22,33,39,28,23,38,34,31,36,35,37,30,25,40,21,15,20,13,2,4,7,16,14,5,12,19,10,6,1,0,9,18,8,3,17,11}, + {22,29,32,41,21,28,38,26,36,31,34,33,25,35,27,24,30,40,37,39,23,12,15,2,9,13,1,7,11,18,20,4,16,6,17,19,8,5,10,14,3,0}, + {24,37,23,40,21,31,36,38,34,26,41,29,28,39,25,30,32,35,27,33,22,19,13,7,2,6,17,11,9,3,5,1,8,4,18,20,15,0,12,10,14,16}, + {26,33,21,36,38,40,37,27,34,41,22,30,29,39,24,23,35,25,28,31,32,6,14,20,4,2,16,19,8,12,7,10,17,1,15,9,3,5,13,11,18,0}, + {28,23,39,34,30,27,37,29,32,40,31,41,24,36,25,38,35,33,22,21,26,2,6,3,16,0,20,18,13,5,12,14,17,7,4,15,11,1,10,8,19,9}, + {32,24,35,31,37,33,29,28,22,38,25,34,23,36,40,21,27,26,39,41,30,8,4,20,14,5,2,19,7,13,16,6,0,9,15,17,12,3,11,18,10,1}, + {28,24,27,29,35,33,26,25,36,21,39,30,32,22,41,37,40,38,34,31,23,17,8,11,13,5,0,7,15,3,18,14,10,4,19,20,2,1,12,9,16,6}, + {29,32,36,22,37,21,31,24,38,30,23,28,26,33,40,39,41,27,34,35,25,11,7,12,14,4,9,0,15,18,6,17,2,16,5,13,10,8,20,19,3,1}, + {33,23,31,26,28,21,30,36,34,22,40,32,25,39,35,38,24,37,29,41,27,8,19,4,3,12,9,20,0,15,11,2,7,13,17,16,6,5,14,1,10,18}, + {38,33,32,29,31,25,35,24,28,27,26,41,34,39,30,23,36,40,22,37,21,2,9,14,0,15,13,6,12,17,20,5,18,10,19,3,8,11,1,16,7,4}, + {23,26,35,32,24,34,27,25,41,36,39,28,37,30,38,40,33,22,21,29,31,11,0,2,6,16,10,9,14,8,15,18,19,1,20,7,13,4,3,17,12,5}, + {30,33,40,36,26,23,39,41,31,38,34,21,25,24,28,37,29,32,22,35,27,7,4,1,9,12,0,11,18,14,3,8,5,15,10,20,16,6,13,2,17,19}, + {32,28,22,34,24,40,29,36,31,33,26,38,37,41,27,39,23,30,35,25,21,3,8,2,18,12,19,11,4,15,7,1,16,20,17,9,13,6,14,5,10,0}, + {26,33,25,38,24,40,34,23,39,29,37,27,30,28,22,36,41,32,31,35,21,5,14,16,11,10,15,12,8,6,2,18,7,4,0,17,1,20,3,9,19,13}, + {25,38,21,40,31,33,37,30,34,36,35,32,23,24,27,39,41,29,28,22,26,15,11,6,13,17,4,1,19,0,7,5,10,16,2,9,18,20,12,8,3,14}, + {30,21,37,31,23,32,29,26,35,22,41,39,27,34,28,33,36,40,38,25,24,15,17,13,6,14,12,3,20,19,18,10,9,2,8,5,0,16,7,11,1,4}, + {30,24,33,37,35,27,29,32,22,39,23,41,25,36,28,21,38,31,34,26,40,11,5,4,1,19,2,12,10,18,9,17,6,8,15,14,0,3,16,13,20,7}, + {22,28,33,38,37,36,32,24,35,25,27,26,30,34,40,31,41,39,29,23,21,14,11,16,0,5,18,4,7,19,10,8,1,17,3,12,2,13,9,6,20,15}, + } + }, + { 43, { + {42,22,40,38,24,23,27,34,33,28,41,25,30,37,29,21,35,32,31,26,39,36,8,19,6,18,9,11,0,12,3,17,13,20,7,16,4,10,14,1,15,2,5}, + {30,36,29,40,22,37,32,39,35,27,33,26,25,31,38,28,24,23,34,42,41,12,7,2,15,8,11,9,17,16,0,10,4,6,3,5,13,21,1,20,14,18,19}, + {34,22,29,32,27,23,38,28,42,25,40,35,26,31,36,24,30,37,41,33,39,0,18,2,12,16,7,4,11,20,14,19,5,15,8,6,9,21,10,13,17,1,3}, + {40,30,42,26,21,27,34,29,38,33,31,35,25,23,32,28,39,36,24,37,41,22,11,20,7,1,5,8,12,9,2,15,6,17,13,18,4,14,19,10,16,3,0}, + {40,28,26,23,32,25,30,21,39,36,29,22,27,37,41,31,38,34,42,33,24,35,13,0,9,18,4,12,1,10,19,5,17,15,11,16,8,20,7,6,14,3,2}, + {22,25,24,35,38,42,40,36,31,33,26,37,39,41,28,30,32,23,34,29,27,1,15,17,14,7,16,13,20,2,19,8,3,18,11,21,10,4,12,5,0,9,6}, + {30,32,24,31,33,39,36,35,21,29,37,41,25,42,38,40,23,28,27,26,34,22,7,4,9,8,2,10,0,11,6,17,15,20,1,13,3,12,19,16,14,18,5}, + {25,35,23,30,41,24,36,26,37,33,38,27,34,31,28,39,29,40,32,42,22,2,9,14,0,13,18,6,19,17,3,8,12,16,21,1,15,10,7,11,4,20,5}, + {41,27,21,23,40,32,37,30,29,34,38,28,26,35,25,39,31,42,33,36,22,24,5,19,4,14,12,8,0,7,9,18,10,2,17,15,3,13,6,16,20,1,11}, + {39,23,38,30,27,34,24,22,29,35,33,31,36,42,25,32,26,21,37,41,28,40,0,19,11,2,7,18,6,13,8,3,1,12,5,10,16,14,20,4,9,17,15}, + {21,24,40,34,26,25,27,22,33,29,31,39,38,35,28,32,30,42,36,41,37,23,6,16,19,9,2,18,7,3,15,20,5,1,13,11,14,17,0,10,8,12,4}, + {24,34,27,36,30,28,25,35,40,38,33,31,37,23,41,26,39,21,42,32,29,22,0,12,9,2,19,1,11,3,7,14,8,17,16,20,5,18,4,15,6,13,10}, + {36,42,39,26,32,21,27,38,34,40,37,33,22,35,31,28,41,24,30,29,25,23,16,15,4,17,11,2,20,13,1,19,7,5,9,3,6,12,18,10,14,8,0}, + {31,24,27,29,25,28,26,22,35,30,38,41,40,32,34,23,42,36,33,37,21,39,15,13,18,8,1,14,19,0,5,16,7,4,20,10,17,2,6,12,11,3,9}, + {22,40,32,25,29,36,31,23,37,35,28,39,27,24,38,30,42,34,26,33,41,13,21,18,6,10,2,1,15,0,16,19,11,14,9,5,8,17,3,20,7,12,4}, + {39,36,30,32,24,34,26,38,27,42,31,35,23,33,28,25,22,29,41,40,37,20,14,12,17,5,11,2,16,8,3,13,7,1,0,19,15,18,9,21,6,10,4}, + {39,38,36,30,32,22,27,42,31,40,24,26,25,23,37,33,29,34,41,28,21,35,1,10,17,19,5,13,11,3,7,4,2,0,14,16,12,15,18,6,20,9,8}, + {22,39,34,42,26,21,32,36,30,28,38,35,33,29,40,24,23,41,27,31,37,25,4,13,1,6,8,19,9,16,11,7,18,2,0,20,5,17,3,15,12,14,10}, + {22,39,29,27,36,26,24,33,38,30,25,40,23,35,37,41,42,28,31,34,32,19,8,17,3,18,7,5,11,10,16,14,4,21,13,20,12,15,6,1,9,0,2}, + {38,35,33,32,30,27,25,23,41,28,37,40,36,21,24,31,29,26,39,42,34,22,18,12,20,19,16,10,15,1,14,13,8,5,4,0,7,9,3,17,6,2,11}, + {23,29,21,32,35,42,34,38,24,26,37,27,39,22,33,36,28,31,40,25,41,30,14,17,9,15,8,6,1,10,4,3,7,13,2,18,16,20,0,5,12,11,19}, + {27,42,23,34,39,26,36,33,25,22,29,40,32,37,28,35,31,41,24,30,38,10,16,11,3,21,14,13,9,8,19,12,5,7,6,2,18,1,17,4,15,0,20}, + {38,35,26,33,28,23,27,34,29,37,24,21,36,41,30,22,32,42,31,25,40,39,15,2,9,1,4,3,8,12,11,16,13,6,10,17,20,18,5,7,19,0,14}, + {35,30,26,38,32,29,22,41,27,40,21,42,33,24,36,28,39,25,31,34,37,23,6,3,14,13,0,5,7,9,19,2,10,4,12,8,15,20,17,1,16,11,18}, + {35,40,39,38,32,23,34,29,41,30,28,24,26,37,42,25,27,33,36,22,31,4,7,19,10,12,9,16,20,15,18,1,21,5,3,17,6,13,0,2,11,8,14}, + {36,34,24,23,42,38,40,29,22,35,27,32,25,39,41,37,33,30,28,26,31,16,19,5,8,10,13,4,14,12,9,18,7,6,0,15,2,1,21,3,20,17,11}, + {40,21,28,31,38,25,37,42,26,23,29,34,36,24,32,22,30,33,27,39,41,35,10,0,2,5,11,17,12,14,8,3,15,13,16,1,4,6,18,19,9,20,7}, + {22,39,38,31,23,33,24,29,27,26,34,30,25,32,37,35,42,41,40,36,28,13,6,19,2,12,3,18,21,20,10,15,5,17,11,16,0,14,9,1,8,4,7}, + {41,24,31,22,37,27,35,38,40,42,23,26,36,25,33,21,34,32,39,30,29,28,16,10,13,6,3,18,19,1,8,2,11,14,9,17,0,4,7,5,15,12,20}, + {33,41,35,26,38,30,40,25,34,29,39,31,36,28,32,24,42,23,37,22,27,17,13,11,19,21,2,14,18,12,4,15,8,3,0,6,20,16,10,1,5,9,7}, + {37,24,22,34,29,32,39,30,41,35,26,31,42,27,40,38,36,33,25,28,23,13,11,15,3,7,10,12,19,0,16,20,4,2,21,6,8,1,14,17,5,18,9}, + {34,36,40,29,24,41,39,26,22,42,38,23,27,30,35,31,33,32,37,28,25,8,15,10,17,11,13,0,2,1,18,20,4,19,14,5,21,9,7,6,16,3,12}, + {33,42,31,36,24,22,40,35,30,28,21,39,27,38,26,37,34,41,25,29,32,23,13,9,16,8,0,18,15,11,10,14,2,5,1,3,12,7,20,6,4,17,19}, + {31,28,22,35,34,38,29,41,32,21,42,36,25,33,37,40,26,24,30,39,27,23,11,13,5,8,19,7,6,14,4,9,17,20,0,2,1,3,18,12,10,15,16}, + {42,31,22,41,26,37,32,38,35,40,34,27,23,33,28,30,36,29,25,21,39,24,7,12,4,11,14,10,3,1,17,15,8,13,5,16,6,18,20,2,9,19,0}, + {34,41,33,23,27,26,40,39,29,37,25,42,31,36,32,38,28,35,30,22,24,18,3,14,6,16,13,15,12,10,2,8,20,17,7,21,5,11,1,19,9,4,0}, + {27,33,40,38,24,39,25,22,30,29,35,31,26,42,34,32,37,41,28,36,23,20,1,13,0,19,15,8,4,3,7,18,12,2,17,21,5,10,14,11,16,9,6}, + {34,31,39,35,30,41,26,38,25,23,22,32,24,37,40,42,33,36,28,27,29,2,17,12,8,5,16,9,18,15,10,14,21,6,4,13,11,19,7,20,0,3,1}, + {27,26,41,28,40,25,22,29,24,39,42,36,31,33,37,23,30,34,32,35,38,17,19,7,3,16,4,0,5,1,14,11,10,18,9,6,2,20,13,21,12,15,8}, + {36,33,29,23,26,24,38,25,27,35,41,31,42,37,32,39,34,28,30,40,22,16,10,19,14,2,9,11,0,20,4,8,15,3,21,7,1,12,6,13,18,17,5}, + {25,33,42,39,41,30,24,32,31,22,29,27,36,23,34,28,40,21,38,35,26,37,9,20,8,13,18,16,7,5,10,17,2,1,15,14,0,19,12,3,11,4,6}, + {39,33,38,25,41,24,35,31,28,26,22,34,42,30,37,27,32,23,40,36,29,10,1,4,6,18,20,15,5,2,9,7,14,21,19,17,0,13,8,11,3,12,16}, + {25,28,27,39,41,30,34,29,26,24,42,38,32,35,33,36,31,40,37,22,21,23,20,13,5,18,11,10,9,17,1,7,16,14,6,3,15,19,8,12,4,0,2}, + {22,41,32,40,31,28,30,37,36,29,38,24,21,35,27,25,39,33,26,34,23,42,18,14,3,16,20,8,12,4,15,6,19,5,2,10,9,0,7,17,1,11,13}, + {37,32,22,21,38,29,33,27,36,42,25,30,26,35,24,40,34,41,28,31,39,23,15,0,5,9,17,1,18,8,13,16,3,19,7,14,2,12,20,10,6,11,4}, + {41,28,35,24,30,40,34,37,25,42,21,23,38,36,32,22,31,27,29,26,39,33,5,7,6,14,4,8,10,13,9,1,0,20,19,16,11,3,15,17,12,18,2}, + {37,39,38,42,26,34,25,27,31,33,29,24,22,41,35,28,30,32,36,40,23,20,1,7,0,6,8,18,3,12,17,13,10,19,9,11,4,14,16,21,5,15,2}, + {24,36,26,38,29,35,23,37,39,41,28,33,25,27,32,30,22,40,34,42,31,20,13,2,14,10,5,3,15,4,18,12,21,7,17,9,1,11,6,8,16,19,0}, + {42,39,37,40,28,36,35,27,33,31,24,22,32,26,41,25,23,34,30,29,38,16,21,18,15,0,17,4,3,19,10,20,2,1,13,11,7,6,5,14,8,12,9}, + {42,38,34,37,24,29,39,28,30,21,26,41,25,32,23,35,22,31,27,36,40,33,12,11,1,8,19,4,18,6,20,2,13,15,0,10,14,3,7,9,16,5,17}, + {22,29,32,31,30,27,40,39,25,36,34,33,38,26,42,37,41,35,23,28,24,19,6,9,7,4,11,0,14,10,13,15,12,18,3,16,20,8,1,5,21,17,2}, + {24,41,39,42,31,34,37,22,35,25,30,23,32,29,28,36,38,27,33,40,26,18,5,3,2,7,12,6,21,9,13,17,11,4,15,0,14,10,20,16,1,8,19}, + {35,23,32,38,40,33,31,26,39,22,30,24,28,37,25,36,34,21,27,41,29,42,17,20,0,19,9,15,4,2,7,10,6,1,5,12,14,13,18,8,16,3,11}, + {28,27,36,25,32,39,23,35,30,40,31,29,24,22,38,26,41,37,42,34,33,4,13,0,5,21,11,1,16,10,18,15,3,9,8,17,12,6,20,19,7,14,2}, + {27,35,30,36,22,21,39,31,26,24,40,32,42,41,33,29,38,25,37,34,28,23,16,6,9,1,3,14,4,18,13,2,8,5,20,17,19,12,7,11,0,10,15}, + {31,41,27,38,23,37,32,28,30,26,36,39,24,42,25,35,22,33,29,34,40,20,5,4,6,2,8,16,7,0,15,10,21,13,9,19,18,1,3,14,11,17,12}, + {25,34,26,38,31,24,29,35,40,21,28,33,41,30,22,39,23,32,27,37,36,42,17,20,16,5,12,8,7,15,1,3,11,19,14,13,4,0,6,10,9,2,18}, + {30,42,34,23,41,29,38,35,26,37,39,27,32,28,24,40,22,36,25,33,31,11,3,10,19,15,8,21,6,5,14,4,12,0,16,7,17,9,13,20,18,2,1}, + {30,32,22,25,24,41,34,23,27,29,31,33,42,28,35,38,40,39,37,26,36,2,16,7,20,13,19,21,3,12,0,11,1,10,6,14,17,18,15,4,9,5,8}, + {28,36,26,39,41,40,37,23,29,33,27,22,32,25,30,35,38,24,42,34,31,20,3,16,6,1,8,14,9,18,15,12,2,5,19,17,10,13,7,21,0,11,4}, + {22,21,39,26,36,38,30,40,42,31,23,41,27,33,35,29,25,28,32,34,24,37,7,17,3,16,0,2,15,18,13,9,4,12,11,14,5,19,10,20,6,1,8}, + {34,25,42,41,37,24,23,39,22,36,33,40,35,30,28,38,27,31,26,29,32,18,9,12,19,13,21,0,7,2,20,6,3,10,4,17,11,1,14,5,16,8,15}, + {39,38,23,31,24,32,37,25,29,35,28,34,26,22,41,27,33,42,40,21,30,36,4,20,8,13,18,17,3,14,11,1,7,19,2,5,12,9,0,10,15,6,16}, + {22,37,42,41,30,35,26,38,28,40,34,27,29,32,31,25,33,23,36,39,24,8,12,11,5,16,14,19,1,18,10,20,4,21,2,7,6,15,0,17,9,3,13}, + {32,34,40,30,33,26,36,31,38,37,28,35,41,29,23,39,27,25,42,24,22,15,8,13,4,9,18,12,2,11,1,21,5,6,14,20,19,17,0,7,10,16,3}, + {35,30,27,23,41,21,28,37,34,36,40,32,39,29,26,24,25,38,42,33,31,22,2,16,0,17,5,9,12,15,10,18,4,13,19,11,7,20,8,1,14,3,6}, + {33,24,35,29,41,26,22,25,27,38,40,30,37,36,31,28,39,32,34,23,42,16,12,17,20,15,7,18,14,19,4,1,11,5,13,10,9,3,8,6,21,2,0}, + {29,27,34,39,36,25,35,26,22,32,42,40,28,30,41,38,33,37,24,23,31,5,0,2,13,11,3,14,21,17,9,18,7,6,15,12,20,1,10,4,8,19,16}, + {29,31,21,36,30,27,33,22,34,37,42,25,24,41,23,28,39,26,40,35,32,38,0,18,5,20,13,12,15,11,10,8,7,1,6,16,19,3,2,4,14,17,9}, + {36,28,40,25,31,22,39,33,23,30,35,34,26,29,42,38,27,24,41,37,32,20,9,18,7,6,10,16,15,0,11,13,1,12,21,2,4,8,14,19,5,3,17}, + {26,37,25,40,22,31,33,35,41,38,23,32,36,34,29,39,42,28,30,24,21,27,5,10,6,19,16,14,17,12,11,1,18,9,13,0,7,4,3,2,15,8,20}, + {22,36,23,35,34,40,25,27,37,42,39,30,26,31,29,41,24,32,28,33,38,9,0,10,5,6,19,17,16,14,11,21,15,2,4,8,1,3,20,18,12,7,13}, + {29,24,23,32,25,39,36,27,34,31,35,42,41,33,38,40,26,37,28,30,21,22,12,5,19,0,16,20,13,6,9,7,3,10,14,8,15,2,4,1,18,17,11}, + {33,38,30,32,26,34,28,39,35,42,25,41,36,22,29,37,24,31,40,23,21,27,0,5,1,6,10,11,17,14,8,4,20,13,9,18,3,19,2,12,15,7,16}, + {27,39,25,31,23,42,36,41,29,35,40,32,37,28,24,30,38,34,33,22,21,26,9,3,12,20,10,0,11,2,1,6,16,4,8,14,13,18,15,17,5,19,7}, + {31,25,24,27,32,42,37,41,38,29,33,35,39,34,40,22,23,30,36,28,26,2,18,7,21,12,9,3,19,5,17,11,6,10,13,0,15,4,1,8,14,16,20}, + {31,29,21,25,37,27,33,32,41,24,23,40,35,30,28,38,34,36,42,39,26,22,11,8,13,1,12,4,7,6,19,3,16,5,17,0,15,14,18,9,2,20,10}, + {29,35,30,22,40,26,39,31,33,38,42,32,25,37,24,36,27,41,23,28,34,14,17,8,10,4,2,19,16,7,21,9,0,18,6,1,3,20,11,13,15,12,5}, + {25,29,28,36,23,41,31,26,38,22,27,24,33,30,35,34,37,40,42,32,39,20,10,5,19,15,18,3,8,2,9,0,6,17,12,7,1,11,13,16,4,21,14}, + {28,39,42,37,33,24,23,27,22,25,40,31,41,30,36,35,29,34,26,38,32,7,6,17,15,12,8,5,13,19,9,2,1,10,20,11,21,14,18,4,0,3,16}, + {33,40,42,24,27,30,34,26,22,37,25,23,21,35,38,31,41,36,29,28,39,32,8,10,19,6,17,2,18,16,0,12,14,20,4,3,7,5,11,1,13,15,9}, + {40,22,41,31,30,36,26,35,27,25,39,29,33,23,34,28,38,24,32,37,21,42,18,17,4,3,2,1,11,6,9,20,10,0,14,12,5,13,16,19,8,15,7}, + {39,24,36,31,26,28,23,21,33,32,35,22,34,30,40,38,37,25,29,41,27,42,11,4,10,0,2,9,20,3,1,15,12,6,19,5,18,16,8,17,7,14,13}, + {34,41,40,33,38,36,30,39,28,21,37,22,32,26,35,27,24,23,29,25,42,31,1,2,14,11,20,0,18,10,8,6,3,16,19,7,4,9,15,17,13,5,12}, + {22,30,35,42,41,33,36,23,38,24,31,37,25,39,32,34,26,28,27,29,40,20,7,19,18,5,11,3,14,1,6,10,4,8,0,13,17,15,9,16,12,21,2}, + {23,36,25,41,31,30,29,34,40,28,37,42,38,26,21,32,27,39,33,22,24,35,4,12,8,14,7,11,16,5,3,15,17,9,19,18,1,10,13,20,0,2,6}, + {27,35,42,40,39,30,24,26,22,37,33,28,36,25,29,34,32,31,23,41,38,3,16,7,6,15,5,17,11,13,20,9,18,10,14,12,1,0,8,4,19,21,2}, + {40,36,42,28,23,30,25,27,39,32,35,26,34,41,33,29,22,24,38,31,37,20,9,2,4,17,5,7,12,6,13,15,14,11,3,19,1,18,0,16,10,21,8}, + {27,22,36,38,41,32,30,35,34,39,29,28,33,42,23,40,25,24,37,31,26,20,16,5,10,8,7,14,12,15,21,13,19,3,18,2,6,17,9,0,11,4,1}, + {28,31,24,39,42,36,40,34,21,35,32,30,23,25,29,26,33,27,37,41,38,22,16,14,6,19,12,4,10,17,0,7,18,5,2,8,15,1,20,3,11,13,9}, + {24,27,39,31,26,42,38,34,23,40,22,29,41,25,21,28,33,30,36,32,35,37,12,18,7,16,10,8,17,14,0,5,3,13,2,20,15,6,4,9,11,1,19}, + {31,22,26,38,41,40,36,25,37,32,28,34,24,39,33,29,27,42,35,30,21,23,13,15,19,9,6,14,7,16,12,17,8,10,4,2,18,1,5,20,0,3,11}, + {25,41,23,33,35,38,28,31,30,42,26,34,37,40,27,36,22,29,24,32,39,18,20,13,5,7,19,16,1,12,9,0,21,14,17,11,6,10,8,4,2,15,3}, + {26,36,29,31,25,39,32,24,22,42,35,23,34,28,41,33,38,27,37,21,40,30,13,11,19,1,8,3,20,10,18,17,15,14,6,0,9,7,5,16,4,12,2}, + {32,26,28,24,39,42,23,30,36,22,27,33,40,31,37,25,38,34,29,41,35,19,8,1,9,16,12,14,11,18,15,21,4,10,2,17,0,5,3,20,13,7,6}, + {26,37,33,38,41,30,25,24,40,42,36,31,27,39,21,29,32,34,22,28,23,35,6,11,5,20,3,17,9,4,1,0,14,2,7,16,12,10,8,19,13,18,15}, + {36,27,42,38,22,29,26,28,37,32,30,23,34,40,39,35,25,41,24,31,33,12,16,7,1,14,0,2,11,3,20,9,21,13,19,17,10,5,8,4,6,15,18}, + {35,34,25,37,32,23,33,28,27,36,29,26,42,22,38,30,41,40,39,24,31,12,8,11,19,7,13,16,2,0,14,3,21,1,20,4,6,18,10,9,15,5,17}, + {30,23,21,27,41,38,31,25,40,33,28,37,36,42,26,39,29,35,32,24,22,34,9,17,8,16,4,6,11,7,1,15,5,12,20,14,13,2,19,10,18,3,0}, + {42,34,37,29,41,30,21,25,22,28,39,27,38,40,31,26,36,32,24,33,23,35,18,17,4,15,9,14,11,2,7,1,10,6,19,8,13,16,5,12,0,20,3}, + } + }, + { 44, { + {43,22,41,40,39,25,42,34,29,35,24,37,31,26,33,38,28,23,32,36,27,30,20,7,2,16,8,5,0,12,10,21,9,11,6,14,4,3,13,1,19,17,15,18}, + {39,22,41,40,42,31,26,30,35,38,27,33,37,29,32,43,36,24,28,23,34,25,21,18,5,12,10,4,13,17,6,19,9,2,7,0,20,3,16,1,15,14,8,11}, + {25,39,29,38,33,28,32,34,22,24,27,30,23,35,26,40,37,36,31,42,41,43,19,21,13,18,5,15,2,17,11,7,14,8,16,10,0,3,20,4,1,12,9,6}, + {29,23,33,27,30,43,38,25,41,32,24,26,34,31,40,35,39,28,36,42,37,22,9,18,19,5,10,13,20,4,21,7,1,16,12,3,15,11,8,6,0,2,17,14}, + {29,27,33,32,26,36,39,24,34,31,37,35,30,40,23,28,43,42,25,38,22,41,5,13,12,14,18,16,15,7,4,10,21,19,2,17,0,3,6,9,20,1,11,8}, + {32,35,33,26,28,38,25,36,34,40,29,22,30,43,23,27,39,24,42,41,31,37,4,18,0,7,9,15,17,14,5,16,2,10,12,1,3,8,21,6,20,11,13,19}, + {24,32,37,23,30,42,36,34,25,38,29,27,28,39,22,35,33,43,40,31,41,26,14,18,13,16,9,11,21,20,12,19,10,8,5,1,6,2,4,7,3,15,0,17}, + {29,42,32,39,34,36,35,43,40,25,27,33,28,37,30,24,26,23,31,41,38,22,7,6,16,3,19,14,10,17,9,1,18,11,13,21,4,2,20,15,5,0,8,12}, + {29,23,31,40,24,39,43,32,37,28,30,36,34,25,41,38,33,27,35,26,42,22,14,11,13,8,15,18,16,12,5,2,10,3,1,4,21,17,20,0,6,9,7,19}, + {40,22,41,38,35,25,43,30,42,28,24,36,32,34,31,27,39,33,37,23,29,26,5,1,17,10,2,21,0,14,7,15,12,20,13,18,6,8,3,11,16,9,4,19}, + {33,31,34,28,23,25,27,32,29,41,37,39,43,40,35,26,42,38,24,30,22,36,21,9,5,4,15,0,3,19,16,2,6,8,20,12,18,13,10,17,11,7,14,1}, + {34,28,36,23,37,25,41,26,32,43,40,31,35,27,29,42,24,22,33,38,30,39,11,10,7,3,14,4,6,2,19,16,13,5,9,17,21,8,12,1,18,20,15,0}, + {29,37,26,28,34,27,41,39,36,30,32,35,33,22,40,23,38,31,24,43,25,42,21,2,1,13,20,4,9,3,12,5,11,7,10,15,18,14,16,19,0,8,17,6}, + {25,27,42,30,36,32,43,33,29,38,41,23,22,26,24,34,37,39,31,28,40,35,0,17,18,16,13,12,3,9,1,15,8,7,6,21,14,4,11,5,20,19,2,10}, + {32,27,22,35,29,42,38,37,28,33,31,24,36,30,39,43,40,23,41,26,25,34,19,8,12,4,18,17,7,3,14,9,16,20,0,5,2,21,11,1,6,10,15,13}, + {25,23,40,36,31,35,30,34,41,38,32,37,43,29,26,28,33,39,42,27,24,22,17,4,21,12,7,19,3,14,13,10,6,5,8,16,20,0,15,1,9,18,11,2}, + {26,25,40,42,34,28,37,22,27,29,38,30,35,31,24,33,23,41,39,32,36,43,19,5,14,4,17,8,21,18,9,16,7,0,11,20,2,1,3,6,12,15,10,13}, + {37,24,30,33,35,28,26,32,39,23,43,41,34,36,38,42,29,27,22,40,31,25,7,20,4,10,9,6,0,8,15,14,17,5,11,18,21,16,19,12,3,2,13,1}, + {37,31,29,26,28,40,25,24,38,35,34,27,23,30,32,42,41,43,36,39,33,22,21,13,10,5,4,3,11,16,15,2,7,0,17,19,6,1,12,14,8,18,9,20}, + {40,29,37,26,25,32,39,27,38,31,35,33,24,43,34,41,23,28,42,30,36,22,6,21,20,2,1,4,17,0,19,3,15,14,7,13,9,8,16,12,11,18,5,10}, + {22,27,37,26,36,42,32,40,31,43,25,24,33,30,35,34,23,39,41,29,38,28,10,14,13,20,9,1,7,15,6,16,11,4,19,2,12,18,0,5,8,3,17,21}, + {31,28,25,33,24,32,43,30,38,36,26,35,41,22,27,40,34,39,37,29,42,23,16,18,0,17,1,15,11,10,3,21,6,13,2,7,20,8,4,14,5,9,19,12}, + {32,30,42,34,37,35,29,27,26,23,41,33,38,40,36,39,28,25,22,43,31,24,16,5,12,17,8,7,11,15,14,20,2,18,3,9,10,1,0,6,13,4,21,19}, + {34,38,24,39,35,32,26,37,42,22,25,41,23,27,29,43,31,33,40,36,30,28,18,10,0,14,17,3,7,21,2,16,20,8,19,6,12,4,1,13,15,11,5,9}, + {39,24,22,36,33,25,28,23,27,31,32,35,34,41,37,30,43,29,42,40,38,26,21,20,2,19,1,18,13,7,17,10,15,11,0,12,16,6,5,14,9,4,8,3}, + {22,25,39,41,40,32,27,29,42,36,26,35,34,30,33,31,37,23,38,43,28,24,14,1,21,3,9,11,15,12,4,16,19,7,6,17,10,8,0,20,5,18,2,13}, + {36,25,40,42,24,30,43,35,33,31,28,32,38,41,22,39,34,29,23,27,26,37,16,11,9,4,0,15,20,14,8,6,1,10,17,19,5,3,13,7,12,2,21,18}, + {42,35,29,41,40,27,25,34,30,23,33,43,24,26,39,31,37,32,36,38,28,22,2,12,8,7,18,4,0,9,1,14,13,17,20,5,10,21,16,19,15,6,11,3}, + {29,24,23,26,43,36,39,42,31,33,41,34,28,30,35,38,32,25,27,40,22,37,2,4,12,18,21,3,15,10,8,5,14,13,11,20,16,1,6,19,0,9,17,7}, + {39,33,25,24,35,42,38,31,41,37,34,26,43,28,36,40,27,23,32,30,29,22,21,18,9,1,7,13,11,16,12,2,0,6,17,3,15,5,20,19,14,4,8,10}, + {35,34,33,22,40,25,28,23,39,37,32,30,38,43,36,24,42,26,41,27,31,29,2,17,5,12,7,1,20,0,4,15,9,14,11,10,13,21,8,19,6,18,3,16}, + {29,31,38,36,27,37,39,30,41,35,23,24,22,26,28,40,42,34,33,43,25,32,21,0,15,3,13,7,9,6,19,1,18,8,10,17,2,14,11,5,20,4,16,12}, + {28,42,31,36,26,39,24,29,35,38,40,37,30,33,43,25,23,34,32,22,41,27,2,6,4,15,0,5,7,1,21,11,14,18,9,8,10,17,16,12,3,20,19,13}, + {22,24,32,29,34,28,39,41,25,36,27,38,33,31,35,37,23,30,43,42,26,40,11,8,18,21,10,20,9,2,13,17,3,12,16,1,5,15,0,6,4,7,19,14}, + {41,43,42,37,29,22,35,40,24,39,25,33,28,36,27,32,26,31,38,34,30,23,20,13,2,7,16,19,15,4,3,17,5,11,14,6,9,12,18,21,10,0,8,1}, + {30,36,39,28,42,37,27,38,41,29,23,33,24,35,31,26,40,22,25,34,32,43,11,10,16,13,9,6,12,8,18,4,20,2,19,5,17,1,3,21,7,15,14,0}, + {43,27,26,28,41,35,31,23,42,24,40,38,33,32,37,34,22,36,39,30,29,25,14,18,15,13,7,19,11,1,4,20,17,9,2,0,8,5,12,3,21,10,16,6}, + {36,28,33,38,30,22,39,26,23,40,31,37,42,29,34,25,32,43,35,27,41,24,13,20,14,18,11,9,12,3,5,17,6,8,10,21,15,7,2,4,16,1,19,0}, + {28,27,32,39,35,38,29,26,43,37,36,25,33,31,41,30,42,34,40,24,23,22,12,6,5,16,2,18,0,14,9,4,13,11,1,3,15,19,21,10,7,8,17,20}, + {40,24,34,31,30,29,41,36,35,33,39,37,26,22,32,42,27,43,38,25,28,23,21,1,14,12,11,20,7,15,6,4,9,0,5,8,10,17,16,3,13,19,18,2}, + {24,38,29,28,26,39,25,23,31,42,33,32,35,30,43,27,36,41,37,22,34,40,1,12,17,10,2,13,3,11,5,8,15,9,18,7,16,20,0,14,6,19,21,4}, + {38,23,30,25,42,31,33,32,36,34,28,22,26,37,24,39,43,41,29,35,40,27,14,20,10,18,6,9,0,11,1,5,3,15,13,16,2,4,21,12,19,8,7,17}, + {24,33,41,29,34,42,31,39,36,26,43,38,40,35,25,23,22,32,28,30,37,27,16,19,6,11,20,0,9,18,12,8,7,1,21,5,17,3,10,13,4,2,15,14}, + {32,29,23,26,24,38,27,41,39,34,40,25,33,37,35,30,22,42,36,31,43,28,8,13,4,17,19,6,20,3,14,12,0,16,9,11,18,1,5,10,2,7,21,15}, + {27,36,38,41,22,29,25,24,40,26,31,43,34,23,32,42,37,28,39,30,33,35,21,15,9,20,0,19,3,8,12,10,5,2,4,7,13,16,6,18,11,14,17,1}, + {43,40,35,31,22,30,37,41,25,33,24,27,42,36,29,28,34,23,38,26,39,32,11,6,14,19,16,3,2,4,20,9,18,5,17,12,21,15,7,10,1,8,13,0}, + {30,26,42,32,39,22,34,27,38,28,40,33,41,37,24,36,31,25,35,23,43,29,3,20,19,11,2,4,8,17,16,7,5,10,13,21,0,18,15,12,14,6,1,9}, + {41,22,32,30,43,31,24,27,42,35,34,40,33,26,39,36,38,28,29,37,23,25,19,14,12,6,11,1,17,4,3,21,2,9,0,20,5,10,15,13,8,18,7,16}, + {30,40,22,37,23,29,28,26,25,43,34,39,36,32,27,31,33,38,41,24,42,35,21,0,17,20,19,7,10,15,5,13,18,3,1,11,6,12,4,2,9,16,8,14}, + {38,24,37,23,42,25,27,33,39,30,41,40,35,26,31,28,34,36,32,29,22,43,19,10,0,16,14,6,1,7,9,3,2,20,18,11,13,5,17,21,8,15,12,4}, + {31,35,28,37,41,27,39,42,38,34,23,33,40,29,26,30,36,24,22,25,32,43,13,20,19,2,7,3,0,11,9,12,10,14,1,6,16,18,4,17,15,21,5,8}, + {42,37,30,26,34,31,41,27,22,28,43,29,24,40,23,32,25,35,33,36,39,38,10,13,7,21,4,19,1,9,15,20,16,12,0,6,3,5,14,11,17,8,18,2}, + {31,41,27,34,32,22,36,23,29,26,28,25,24,42,37,43,39,38,40,33,35,30,20,0,5,1,18,17,8,13,2,4,21,15,3,14,10,19,12,9,16,7,11,6}, + {24,26,41,28,35,31,40,37,39,22,27,32,34,43,29,23,33,38,30,36,42,25,10,12,19,20,17,4,1,11,8,6,2,18,15,13,9,7,0,5,3,21,14,16}, + {28,26,29,25,23,33,31,22,30,41,32,35,34,36,39,37,42,27,38,43,40,24,15,2,11,20,14,10,19,9,8,12,5,7,21,4,6,18,17,13,1,16,3,0}, + {24,32,35,28,31,39,25,29,38,33,41,26,43,37,34,30,36,40,23,27,42,22,17,12,7,21,6,5,18,13,20,14,10,4,9,19,2,1,11,8,3,0,16,15}, + {41,32,35,23,30,26,34,28,24,36,27,38,42,31,37,22,40,33,43,25,29,39,8,16,3,11,9,0,19,13,10,1,17,6,14,4,7,5,18,21,15,20,2,12}, + {42,31,30,32,41,35,23,25,36,34,28,33,26,39,38,43,37,22,40,24,27,29,21,6,19,3,14,1,9,8,11,20,15,2,0,16,7,18,13,10,17,5,12,4}, + {25,30,38,42,28,35,37,41,29,24,23,34,31,22,33,43,39,26,40,27,32,36,6,11,9,7,17,4,14,16,20,15,18,12,19,13,21,0,2,5,10,1,8,3}, + {23,41,39,32,28,25,33,42,27,34,24,40,37,26,38,29,43,30,35,22,36,31,19,0,20,15,1,8,17,21,4,7,3,14,9,18,16,12,6,13,11,2,5,10}, + {22,24,32,39,27,26,29,38,30,40,42,34,31,43,28,33,41,36,23,37,25,35,21,4,15,11,20,19,12,8,5,16,9,14,6,17,10,1,3,18,7,13,2,0}, + {26,39,43,22,29,38,40,31,27,23,28,37,36,25,24,41,33,35,34,32,30,42,4,6,18,1,17,16,2,0,19,3,20,10,12,21,15,5,8,7,14,11,9,13}, + {41,30,28,40,31,26,29,42,22,43,32,38,25,39,33,27,37,35,24,23,34,36,19,12,2,1,8,3,11,15,5,13,20,18,21,14,16,7,9,17,0,10,4,6}, + {25,31,30,40,28,39,27,38,41,22,35,24,29,26,36,43,33,37,34,32,42,23,14,0,20,9,18,8,13,3,12,4,6,17,10,21,5,11,19,2,15,7,16,1}, + {25,39,29,22,32,36,38,27,40,35,23,42,26,41,34,37,43,30,24,28,31,33,13,8,15,0,6,19,10,2,12,17,11,5,14,21,3,18,4,9,1,16,7,20}, + {43,24,42,27,40,30,23,37,39,41,28,33,32,35,26,31,36,29,34,25,38,22,19,7,1,10,12,16,8,0,18,20,2,9,17,3,13,5,21,15,4,14,6,11}, + {25,27,22,33,41,43,36,42,23,31,29,34,39,37,30,35,24,26,40,38,28,32,17,4,7,3,9,16,10,19,12,14,20,8,13,5,1,6,15,2,0,21,18,11}, + {25,22,41,29,32,40,34,27,43,30,36,24,42,39,33,35,37,23,26,38,28,31,18,7,11,0,6,19,13,5,2,9,15,4,1,20,16,12,14,3,8,10,17,21}, + {29,36,30,33,39,35,38,40,25,27,41,22,34,31,24,32,43,37,28,42,26,23,17,2,7,13,19,8,5,14,20,15,10,9,3,11,0,18,1,12,6,4,16,21}, + {39,36,24,27,31,23,41,43,38,42,25,22,34,26,40,30,35,29,32,37,33,28,6,3,8,10,2,0,11,17,19,14,5,9,21,16,1,7,13,20,4,15,18,12}, + {40,22,35,42,30,27,33,29,34,25,31,24,38,41,36,43,37,39,26,32,28,23,21,11,2,10,13,7,1,16,3,5,12,4,6,14,19,0,9,20,17,15,8,18}, + {41,26,42,37,27,30,22,34,38,25,40,23,31,39,36,32,28,43,33,29,35,24,20,18,2,21,12,17,3,7,6,9,8,4,0,10,19,13,1,16,14,5,11,15}, + {26,31,36,32,23,28,33,35,34,40,43,39,37,30,27,22,42,25,38,41,29,24,15,6,8,10,3,5,17,12,7,1,13,9,2,16,0,11,19,14,21,18,4,20}, + {32,42,33,39,36,27,31,26,24,43,28,35,38,37,25,41,23,30,34,40,29,22,21,14,11,3,5,8,16,2,7,12,9,19,0,6,4,17,1,13,10,20,18,15}, + {40,27,41,35,32,30,42,25,24,39,23,37,33,31,26,28,43,34,22,29,38,36,21,17,8,19,0,2,14,20,9,16,18,3,7,11,1,13,6,4,12,10,15,5}, + {28,42,40,23,25,32,31,29,24,43,30,34,38,27,41,26,35,36,33,37,22,39,21,4,1,3,6,9,5,8,17,11,19,16,14,10,13,15,7,12,18,20,2,0}, + {34,43,35,37,24,36,30,42,23,27,25,40,38,29,41,28,32,39,22,33,26,31,13,11,4,16,18,9,15,19,5,1,3,14,0,2,8,21,17,10,6,7,20,12}, + {28,25,34,30,42,32,36,23,37,31,26,40,24,22,35,27,39,29,33,43,41,38,12,21,0,14,16,13,17,9,20,19,2,5,11,7,15,3,8,6,18,4,10,1}, + {28,39,27,31,29,32,41,30,33,42,34,23,40,22,38,24,35,26,37,36,43,25,21,5,10,16,8,12,4,15,19,18,13,6,0,14,20,17,7,9,2,11,1,3}, + {30,39,25,31,38,32,43,34,33,37,41,40,22,42,29,36,24,28,35,27,26,23,1,11,5,8,13,7,4,15,3,20,19,16,2,17,14,6,18,21,0,10,9,12}, + {22,32,42,37,40,38,31,25,34,36,26,35,41,33,24,27,39,23,29,43,28,30,14,21,2,6,8,15,11,3,1,20,5,13,18,17,4,10,0,16,9,12,7,19}, + {24,27,26,33,23,41,38,37,30,32,29,43,39,36,25,34,28,40,42,35,31,22,21,1,16,18,12,15,10,20,3,17,13,4,0,11,7,19,5,8,6,14,9,2}, + {26,37,29,42,28,30,32,40,27,43,39,36,23,25,41,24,33,35,31,22,38,34,10,13,2,5,0,4,11,17,7,19,14,1,12,8,3,16,20,15,18,21,9,6}, + {42,39,31,36,28,43,38,26,37,32,29,25,30,33,23,22,35,41,24,27,34,40,21,0,5,4,3,2,19,8,13,18,11,17,20,9,15,1,14,12,16,7,6,10}, + {22,33,28,37,42,38,34,26,25,23,41,32,40,43,31,30,36,29,35,24,39,27,3,8,5,10,17,15,19,2,12,20,6,0,7,16,4,1,18,14,11,13,9,21}, + {36,40,23,29,22,43,42,27,33,41,25,24,32,38,30,39,34,28,37,35,26,31,2,21,19,15,12,6,5,11,3,18,0,16,4,14,1,10,8,7,13,9,20,17}, + {30,26,38,31,25,37,29,22,39,33,43,24,36,28,40,34,23,42,32,27,35,41,7,6,14,15,8,19,13,3,0,2,16,9,20,4,12,18,21,1,11,5,17,10}, + {25,40,22,42,32,39,36,31,24,38,37,35,33,30,23,41,43,28,26,29,27,34,10,17,7,1,3,20,6,0,15,11,5,8,2,21,16,14,13,9,18,12,19,4}, + {35,27,31,33,36,26,22,39,34,25,30,42,24,43,38,23,40,28,32,29,37,41,13,2,1,6,14,19,0,10,12,5,17,16,11,21,9,7,4,20,3,8,18,15}, + {41,33,24,29,38,30,37,26,43,22,32,25,31,36,34,28,42,39,27,23,35,40,21,4,1,10,18,13,7,16,14,6,8,19,15,20,2,11,0,5,3,12,17,9}, + {35,32,40,39,22,29,26,33,42,24,43,34,31,37,30,23,27,41,36,25,28,38,10,15,5,0,12,6,9,21,3,14,19,11,7,18,16,8,20,4,2,17,13,1}, + {28,41,40,24,38,34,23,36,43,32,26,37,33,25,29,35,22,31,39,27,42,30,21,10,6,20,19,13,18,8,14,2,15,4,9,12,7,16,11,3,1,17,0,5}, + {33,30,23,41,32,42,35,27,38,37,22,39,24,43,34,26,29,31,28,25,40,36,9,2,19,5,18,13,20,11,15,4,12,0,3,1,7,16,8,10,6,14,17,21}, + {43,31,36,33,23,34,25,38,37,30,29,24,42,32,35,40,39,41,27,22,28,26,17,0,2,9,7,19,15,5,13,16,3,14,6,10,18,8,21,20,11,4,12,1}, + {41,33,31,37,27,40,25,32,26,43,38,30,28,34,42,29,35,39,36,24,23,22,11,0,17,10,4,13,5,9,14,6,8,3,15,2,18,7,19,1,21,16,20,12}, + {42,29,37,36,24,40,34,32,38,28,43,23,25,31,39,26,41,30,27,33,35,22,2,21,13,0,10,7,1,3,18,14,12,20,6,17,9,15,5,16,19,4,8,11}, + {33,40,24,26,32,23,41,35,38,25,37,22,31,34,43,28,27,29,42,30,36,39,10,6,9,3,21,0,11,17,12,14,18,20,16,7,13,5,15,19,1,8,2,4}, + {40,39,35,32,36,41,30,33,22,26,23,43,37,24,31,29,25,34,28,27,38,42,14,13,6,16,0,19,10,15,3,1,4,7,11,2,18,9,20,17,12,5,21,8}, + {23,39,31,28,33,37,26,42,34,38,29,40,27,36,41,35,43,25,24,32,22,30,20,19,9,2,16,0,5,4,8,15,10,6,13,12,21,1,7,3,17,11,18,14}, + {33,38,43,30,28,42,36,39,31,41,35,27,22,26,32,25,40,37,34,24,29,23,16,18,1,7,5,13,3,15,4,11,17,8,0,10,2,12,6,20,19,21,14,9}, + } + }, + { 45, { + {44,22,42,41,31,23,43,29,33,37,35,27,36,40,28,30,38,34,32,26,24,25,39,21,2,9,7,19,0,12,15,11,14,8,17,10,16,20,4,1,5,3,13,6,18}, + {42,31,40,28,36,27,37,30,38,43,41,44,33,22,26,29,25,35,32,23,34,24,39,10,16,3,1,17,21,15,14,8,18,12,5,7,4,6,20,13,19,2,0,9,11}, + {40,24,38,28,25,36,39,35,42,37,27,30,41,26,32,22,23,31,34,44,43,33,29,3,6,13,5,18,20,17,12,19,0,16,7,2,1,15,9,21,10,14,4,8,11}, + {27,35,42,37,31,33,36,39,23,44,38,40,25,41,30,28,32,43,26,29,34,24,21,14,2,16,1,8,20,6,18,7,0,17,12,3,13,5,10,9,19,11,4,22,15}, + {24,30,40,37,44,43,27,33,32,41,39,28,36,34,31,35,38,29,42,26,25,23,19,6,12,7,13,8,3,22,16,10,5,9,11,2,14,20,4,15,0,18,1,17,21}, + {27,38,34,28,32,37,36,24,26,31,41,40,25,23,33,35,44,39,29,42,30,43,20,12,21,16,14,0,5,18,13,17,7,19,2,4,3,22,1,9,6,10,8,15,11}, + {41,32,34,37,23,39,25,29,42,33,40,30,43,36,27,38,35,24,26,31,28,44,15,22,19,21,14,9,3,18,4,20,5,13,1,11,17,12,2,8,7,16,6,10,0}, + {39,27,22,33,26,36,28,42,35,44,32,43,31,38,40,37,34,41,29,24,23,25,30,15,20,13,8,16,9,6,4,10,3,14,18,7,0,19,12,11,17,2,5,1,21}, + {34,40,27,43,31,30,41,29,35,26,36,25,44,33,32,37,39,38,24,42,28,23,14,10,20,6,9,2,12,1,15,21,13,4,0,8,22,11,19,7,16,5,17,3,18}, + {38,28,36,43,41,32,26,23,40,35,27,39,44,29,37,34,30,42,24,33,31,25,2,22,7,5,8,10,3,16,4,20,14,17,15,18,21,6,0,11,1,13,9,19,12}, + {32,29,26,41,43,27,24,34,31,39,37,40,38,28,22,33,30,35,44,36,25,23,42,6,0,21,10,7,3,18,2,8,19,15,20,17,12,9,11,5,1,13,16,14,4}, + {43,29,42,34,38,41,31,26,22,32,23,33,36,39,35,37,24,44,40,28,27,30,25,18,9,21,8,13,11,2,4,20,6,1,5,7,19,16,10,17,14,0,12,3,15}, + {26,28,42,41,32,27,44,35,25,40,31,39,37,43,24,30,29,38,36,33,23,22,34,0,2,7,18,13,10,19,16,9,21,12,6,11,3,20,8,17,15,14,5,4,1}, + {32,23,22,31,43,36,29,25,34,27,44,40,30,41,28,26,39,37,33,35,38,24,42,20,11,10,2,13,17,19,9,12,14,8,4,1,0,7,6,5,16,21,3,18,15}, + {44,32,27,43,39,34,31,42,28,33,25,30,24,40,37,26,35,23,29,41,36,38,17,21,1,7,3,2,0,9,13,4,12,6,19,10,14,11,5,8,16,20,15,22,18}, + {35,43,25,36,29,28,30,24,42,26,41,38,34,31,44,27,32,23,33,39,37,40,15,13,20,10,22,11,14,3,19,18,8,2,17,7,4,16,9,12,0,21,5,1,6}, + {34,27,40,39,31,28,38,33,30,29,35,24,41,37,42,32,26,25,43,23,44,36,12,14,4,16,6,18,0,8,15,22,21,20,19,3,2,5,10,7,9,17,1,11,13}, + {22,28,42,36,25,43,34,39,29,40,24,26,33,30,37,27,23,44,31,35,32,38,41,18,12,20,3,6,15,9,2,7,17,10,16,0,14,19,11,13,1,21,4,8,5}, + {25,29,44,37,34,42,26,43,38,23,33,22,27,36,41,31,28,40,30,32,24,35,39,14,16,19,18,5,1,8,3,11,2,9,17,10,12,4,7,0,6,20,21,15,13}, + {36,23,39,32,44,42,24,35,43,37,31,33,41,29,22,26,40,27,34,28,38,30,25,14,18,10,20,7,16,13,0,12,3,15,19,11,21,9,4,8,6,17,5,2,1}, + {28,35,38,43,40,31,37,30,29,27,34,24,32,42,36,26,33,44,39,25,23,41,21,15,0,18,14,8,17,9,3,20,7,6,4,13,1,5,11,10,16,19,2,12,22}, + {23,28,24,40,25,38,37,30,44,34,31,27,33,35,43,42,36,22,26,39,41,32,29,14,15,19,18,11,20,10,21,5,2,12,6,9,1,13,17,3,8,16,0,4,7}, + {41,34,36,40,44,43,28,23,25,32,22,35,39,27,30,37,29,31,38,26,42,33,24,18,2,20,17,0,10,12,5,21,3,19,7,15,8,14,16,11,9,4,1,13,6}, + {26,36,42,30,24,32,35,44,39,22,31,33,41,23,27,29,25,40,34,37,38,43,28,13,18,16,0,11,3,2,10,20,12,21,14,6,15,19,5,8,7,4,1,9,17}, + {32,31,34,25,38,44,39,23,27,41,43,36,24,33,30,28,35,40,26,37,29,42,20,18,12,11,13,22,3,19,10,14,2,21,0,15,7,17,9,1,4,6,5,16,8}, + {38,36,24,34,27,39,32,33,35,23,41,43,31,29,26,25,30,40,44,28,42,37,14,21,2,17,8,15,19,13,4,9,18,7,6,22,1,12,0,11,16,3,20,5,10}, + {44,40,35,37,24,39,33,29,27,26,36,34,31,38,28,32,41,43,42,23,25,30,19,8,3,17,13,12,11,7,2,22,9,5,21,1,15,14,20,6,4,16,18,10,0}, + {29,39,31,30,38,26,40,35,24,28,41,44,36,27,42,25,43,32,34,37,33,23,9,16,2,22,13,18,15,19,10,7,5,3,17,1,6,0,21,8,11,14,4,20,12}, + {34,25,44,35,41,24,29,31,30,32,36,40,26,28,38,43,27,33,42,37,39,23,12,0,16,3,11,13,6,9,7,5,15,10,21,19,17,14,8,4,20,2,1,18,22}, + {24,28,31,33,43,27,36,25,39,41,37,23,38,42,40,29,32,35,26,30,34,22,44,16,14,4,15,7,11,18,17,0,12,2,19,21,13,9,5,1,3,20,6,8,10}, + {38,39,26,37,27,32,34,29,42,25,22,41,30,36,33,43,31,44,24,40,23,35,28,20,3,0,2,4,13,7,21,16,5,14,6,19,10,18,17,1,12,11,8,15,9}, + {33,44,28,36,23,34,39,24,27,25,29,41,32,37,35,31,38,42,30,40,26,43,19,7,11,17,8,12,18,22,20,6,1,9,21,2,15,14,16,3,13,5,10,4,0}, + {35,27,24,41,26,22,28,32,42,36,30,40,44,38,29,39,31,37,33,25,43,34,23,21,4,0,11,17,9,1,6,16,19,2,8,20,3,15,12,10,13,7,5,14,18}, + {34,38,24,29,42,26,32,30,44,28,25,23,43,41,36,39,33,35,37,27,40,22,31,21,1,18,8,13,4,17,12,11,19,16,10,14,0,5,20,3,9,15,2,7,6}, + {25,28,34,27,33,31,42,24,43,23,35,44,36,39,41,30,40,38,37,32,26,29,5,4,20,17,8,14,0,13,22,9,11,15,19,6,3,1,10,7,21,12,2,18,16}, + {27,37,28,30,35,40,26,29,24,43,25,44,39,41,31,36,32,34,33,38,42,23,8,0,12,9,5,10,2,1,20,14,4,18,13,6,17,7,19,22,21,11,3,16,15}, + {34,23,41,25,27,37,35,26,36,43,33,40,28,39,29,38,32,42,31,44,30,24,14,20,16,3,11,19,0,22,8,21,2,7,10,6,1,18,15,13,5,17,12,9,4}, + {31,24,39,44,37,41,32,38,42,35,28,43,27,40,34,23,29,33,22,30,26,36,25,4,6,17,2,12,1,3,14,0,11,10,9,19,16,8,18,20,7,21,15,13,5}, + {30,39,35,29,41,31,33,23,40,25,38,34,27,26,43,24,37,36,28,44,42,32,14,22,4,6,21,1,5,13,16,20,2,0,9,8,19,11,3,17,7,10,18,15,12}, + {35,29,25,40,23,31,34,30,37,32,39,38,41,27,44,36,43,33,26,24,28,42,1,4,11,14,3,6,5,17,13,20,12,22,16,21,7,9,2,18,10,0,15,8,19}, + {27,25,39,41,30,38,36,44,31,33,43,23,28,32,35,37,29,40,34,24,42,22,26,16,6,2,1,0,18,11,4,9,7,15,10,19,8,21,3,17,14,5,12,20,13}, + {22,35,39,37,27,29,41,43,31,28,44,25,32,26,30,36,24,34,40,42,33,38,23,5,3,17,9,21,11,2,20,7,0,8,16,18,13,12,10,19,1,4,15,6,14}, + {31,30,24,35,23,40,36,28,43,38,34,25,33,26,37,39,32,29,44,41,27,42,9,11,22,8,13,18,12,7,1,5,14,6,3,17,16,20,4,15,21,19,0,10,2}, + {31,43,23,40,28,24,41,26,25,39,22,33,30,36,35,27,34,44,38,42,32,29,37,14,21,1,11,5,12,8,7,17,15,19,16,6,10,2,4,18,9,13,3,20,0}, + {44,24,26,28,23,34,40,37,29,31,43,39,36,42,41,35,22,32,38,27,30,25,33,3,13,4,20,9,21,1,0,15,10,17,2,19,14,5,16,11,7,18,12,8,6}, + {24,34,25,26,40,33,29,38,42,35,30,36,39,28,37,32,44,41,31,27,23,43,20,22,18,6,8,21,17,16,3,15,10,12,1,5,7,0,2,14,13,9,4,11,19}, + {42,22,43,41,29,37,26,40,31,44,34,27,24,32,23,25,35,28,38,30,33,39,36,15,18,0,6,14,1,9,3,5,13,16,4,11,21,2,8,17,7,10,19,12,20}, + {38,41,36,35,27,24,44,39,33,43,28,40,29,23,34,32,26,30,37,25,31,42,6,0,12,4,8,2,10,7,15,1,3,18,11,22,17,14,21,9,5,20,19,16,13}, + {32,43,35,26,30,42,31,27,33,28,41,34,23,38,36,44,24,29,39,25,37,40,21,19,1,18,0,8,17,22,11,13,15,3,14,2,6,12,4,10,9,5,20,7,16}, + {34,31,33,39,25,36,29,41,40,24,38,32,22,27,30,35,37,43,23,42,44,26,28,4,10,14,0,13,5,3,18,17,11,1,21,15,7,16,6,8,2,12,19,9,20}, + {23,29,25,30,34,22,37,26,40,43,38,27,31,36,24,39,42,35,28,33,41,32,44,14,17,12,1,3,2,21,19,8,6,5,10,15,4,20,18,16,9,0,11,13,7}, + {38,28,35,31,26,32,41,25,39,43,33,27,24,34,30,36,44,23,40,42,37,22,29,12,3,18,6,16,21,19,10,8,7,9,1,17,4,0,2,11,15,14,20,13,5}, + {24,36,33,40,37,25,39,43,26,30,23,27,34,29,31,41,28,38,32,42,44,35,9,12,21,4,11,22,5,14,20,13,1,8,6,17,0,3,2,15,10,16,18,7,19}, + {25,37,42,29,34,23,27,32,30,24,43,35,41,40,38,33,26,31,44,36,28,39,10,5,21,4,3,9,14,6,1,7,15,13,11,19,8,22,0,2,16,12,18,20,17}, + {24,43,41,44,35,25,38,33,28,40,31,26,34,36,29,23,39,30,32,42,27,37,6,18,16,10,20,7,14,17,8,12,1,5,13,3,22,2,19,9,0,15,4,11,21}, + {34,40,37,41,43,26,35,39,42,33,36,28,22,29,31,30,27,23,32,44,24,38,25,21,7,4,15,0,6,11,5,2,9,16,20,13,3,8,1,12,19,10,14,18,17}, + {31,22,25,40,36,28,39,34,33,24,32,37,30,29,27,42,44,23,41,26,35,38,43,18,10,1,9,21,0,15,11,6,12,17,8,5,3,19,7,14,13,16,20,4,2}, + {27,42,25,31,37,30,24,33,22,32,26,29,38,44,39,28,43,41,34,36,35,40,23,6,12,7,20,14,9,1,11,19,21,2,13,4,8,5,15,0,18,3,10,17,16}, + {44,42,41,38,28,36,32,27,43,22,35,25,39,29,31,37,40,26,24,30,33,23,34,12,0,3,20,6,8,17,11,1,5,9,16,2,10,13,19,14,4,15,21,18,7}, + {34,38,31,43,24,35,27,36,30,42,28,32,26,23,39,33,40,29,25,41,37,44,3,6,10,20,7,14,18,4,15,5,17,9,0,11,19,13,2,22,8,1,12,21,16}, + {39,42,41,33,38,35,28,44,26,43,31,40,32,23,34,30,27,22,36,24,29,25,37,11,8,13,7,1,6,5,10,12,15,9,14,3,18,2,17,0,21,20,16,4,19}, + {37,35,39,26,29,32,27,34,24,40,31,44,36,43,33,25,42,23,41,28,30,38,17,4,7,21,14,11,2,0,5,22,3,19,12,1,16,9,15,20,8,13,6,10,18}, + {34,36,29,32,23,27,31,26,33,42,35,41,24,39,38,37,30,44,40,25,43,28,9,18,10,3,14,7,16,6,21,19,8,17,0,11,13,20,4,12,1,22,2,15,5}, + {29,41,23,33,27,34,39,44,35,37,25,32,42,28,26,36,30,43,38,31,40,24,1,22,9,6,4,21,7,0,16,15,8,10,19,14,2,11,13,3,20,12,18,5,17}, + {29,26,23,33,24,43,30,25,34,22,40,27,28,35,39,44,37,32,42,41,36,38,31,18,0,19,6,2,13,20,16,12,17,10,7,9,4,15,5,14,3,8,11,1,21}, + {22,33,29,40,27,30,39,35,44,43,37,25,34,31,42,28,41,24,26,32,36,38,23,16,12,1,9,15,19,3,7,0,4,13,20,18,11,6,5,21,17,2,14,8,10}, + {41,33,32,30,38,25,28,27,35,31,24,37,23,40,26,36,43,29,39,42,44,22,34,7,9,4,17,20,21,15,0,3,16,8,13,19,2,14,5,12,1,11,10,6,18}, + {29,33,37,34,38,31,22,42,41,28,23,35,30,39,44,43,26,36,32,24,27,40,25,10,7,12,18,17,21,19,2,20,4,16,3,11,6,5,9,13,8,15,1,0,14}, + {23,43,22,32,44,36,30,39,25,37,33,29,42,26,35,31,27,41,24,34,38,40,28,0,19,1,3,11,15,14,16,4,20,2,6,8,17,9,18,7,10,13,21,12,5}, + {40,25,37,24,23,43,41,44,39,32,42,33,29,26,36,28,38,30,35,34,27,22,31,17,6,19,8,21,10,1,14,3,15,18,12,5,13,20,16,2,0,11,9,7,4}, + {23,42,44,36,30,26,34,24,38,43,25,22,28,31,27,33,40,32,35,37,41,39,29,3,5,13,12,14,21,2,1,8,0,7,16,18,17,19,9,15,10,6,20,4,11}, + {23,32,38,30,29,28,25,35,41,33,44,34,31,43,42,40,26,37,27,36,39,22,24,5,16,17,14,18,8,6,11,10,1,21,9,19,2,7,15,4,3,0,12,20,13}, + {31,36,39,43,27,29,38,34,42,30,33,24,35,32,23,40,28,41,37,44,25,26,6,3,17,19,8,1,5,9,22,11,20,15,18,2,10,14,4,12,7,13,0,21,16}, + {25,38,30,24,36,42,22,29,43,40,26,35,31,39,33,23,37,44,27,41,28,34,32,7,9,0,12,1,17,15,2,19,6,14,5,11,13,16,18,4,20,10,21,8,3}, + {25,24,27,36,29,41,33,32,30,40,34,23,44,28,35,43,31,22,37,39,42,26,38,11,13,16,14,19,21,10,18,4,9,1,6,12,7,2,20,3,17,8,0,15,5}, + {24,36,27,29,26,40,42,32,43,34,33,28,25,31,35,30,37,39,38,23,41,22,44,10,6,21,8,2,15,3,11,1,7,19,17,0,9,12,18,13,16,20,14,4,5}, + {25,24,33,38,32,35,30,37,43,26,34,31,44,28,23,42,36,29,27,41,40,22,39,8,18,16,2,1,15,19,5,4,11,6,14,9,3,12,20,10,13,17,0,7,21}, + {23,33,24,26,44,27,42,41,37,34,32,36,29,28,40,25,35,43,31,39,38,30,10,7,16,4,8,12,1,9,22,21,0,15,11,18,5,20,13,3,6,2,17,14,19}, + {41,39,34,33,36,35,31,23,42,25,44,29,27,32,38,40,43,26,28,24,37,30,7,21,9,3,5,8,17,2,22,15,13,19,11,14,1,20,4,12,6,0,18,16,10}, + {43,34,38,25,36,26,44,32,41,27,39,37,42,29,33,40,31,35,28,30,24,23,21,4,0,17,10,7,18,9,1,20,12,11,16,2,19,6,15,14,13,22,5,3,8}, + {24,36,39,30,29,38,32,27,40,22,33,35,43,31,37,44,34,28,41,26,23,42,25,16,14,7,12,10,9,11,15,19,18,20,21,6,3,13,5,2,4,17,0,8,1}, + {28,25,24,29,38,42,44,27,37,31,41,43,40,39,35,32,34,33,26,30,36,23,8,7,2,6,4,20,16,5,1,22,15,19,10,12,0,9,18,3,11,21,17,14,13}, + {34,25,22,41,33,32,26,28,42,23,35,37,39,24,29,43,38,30,27,36,31,40,44,15,21,19,1,5,2,4,18,6,11,9,12,0,16,20,13,3,7,17,8,14,10}, + {41,25,44,33,37,39,43,40,38,31,36,32,24,30,23,29,26,28,42,34,27,35,2,14,8,1,16,21,0,22,3,13,6,9,19,17,10,4,20,15,7,11,18,5,12}, + {32,33,37,28,44,38,40,42,23,29,31,27,34,43,30,39,35,26,25,41,24,36,12,8,16,1,17,9,3,11,14,5,10,19,6,15,0,22,13,20,4,7,18,21,2}, + {40,27,26,44,31,24,41,43,25,29,28,30,36,38,39,33,35,32,34,37,42,23,18,3,5,9,17,0,6,22,12,14,2,7,20,16,21,19,13,4,11,10,8,15,1}, + {26,24,36,43,39,28,37,35,40,23,33,31,34,42,32,25,41,38,44,30,22,27,29,9,18,3,16,6,19,8,0,7,11,2,12,5,13,21,20,4,17,14,10,15,1}, + {23,32,42,26,29,44,36,34,40,24,27,43,39,41,28,25,33,31,30,37,35,38,17,14,9,12,5,7,2,0,6,4,10,1,19,11,21,20,8,16,13,3,18,15,22}, + {34,31,23,25,41,32,30,42,26,43,36,33,44,37,28,27,38,29,40,24,39,22,35,15,19,4,7,5,9,17,16,18,14,2,11,0,21,20,12,3,8,10,1,6,13}, + {34,38,41,25,36,40,42,26,23,28,31,33,43,27,24,30,35,44,37,29,32,39,6,8,22,11,10,1,9,7,5,19,0,13,12,4,2,18,16,20,15,21,17,14,3}, + {29,40,28,32,43,34,36,30,41,33,37,42,35,39,26,24,38,31,23,25,44,27,14,0,11,13,10,7,3,5,19,16,8,15,4,21,1,12,6,2,20,9,22,18,17}, + {30,32,23,27,34,33,28,44,40,39,43,42,25,41,37,31,35,29,38,26,22,36,24,11,8,16,19,3,6,17,5,15,1,18,4,12,21,14,0,2,20,13,9,10,7}, + {30,44,31,43,28,42,27,22,32,38,29,26,35,39,25,24,33,40,34,23,36,41,37,4,15,10,17,6,19,9,3,5,8,11,14,12,7,20,13,21,16,0,2,18,1}, + {33,42,25,37,35,40,26,31,43,41,39,44,38,27,29,32,34,24,36,30,23,28,18,2,11,6,10,21,15,4,17,19,13,7,1,12,3,8,22,9,16,5,20,14,0}, + {25,24,44,23,29,34,33,39,28,27,36,31,38,32,37,30,41,40,43,26,35,42,21,13,7,20,11,0,17,1,15,9,2,16,19,5,8,12,6,18,22,4,3,10,14}, + {37,31,23,33,36,34,25,32,44,26,38,24,39,41,27,43,40,29,28,35,42,30,20,11,8,19,14,4,16,6,15,1,13,7,5,3,0,18,2,9,12,21,17,10,22}, + {28,39,26,30,38,35,27,37,42,40,36,23,33,43,31,25,44,32,34,41,24,22,29,16,8,6,10,2,5,0,21,4,15,20,18,14,17,11,3,7,12,9,1,13,19}, + {42,32,25,24,35,30,33,36,31,29,43,41,26,23,27,34,38,37,39,44,40,28,20,1,22,21,3,0,4,9,11,13,2,6,17,14,10,15,8,5,19,12,18,7,16}, + {34,22,24,30,37,26,29,27,33,32,40,35,38,43,39,31,41,44,28,36,25,42,23,11,9,3,1,12,15,14,5,13,2,20,6,17,10,21,8,19,4,0,16,18,7}, + {23,29,41,38,43,26,33,40,25,31,24,27,42,35,32,39,44,22,30,28,37,34,36,3,10,1,13,20,6,21,17,4,16,19,11,5,8,0,18,9,7,2,12,15,14}, + } + }, + { 46, { + {45,27,42,38,43,31,33,44,41,37,30,36,40,32,24,34,39,29,35,28,23,26,25,17,8,13,19,18,0,21,15,9,5,3,11,4,7,10,14,1,6,20,12,16,22,2}, + {23,36,40,27,24,43,31,38,30,45,32,37,39,33,29,42,34,44,28,25,35,41,26,8,17,7,13,11,5,2,21,15,18,10,6,16,4,1,3,19,14,20,9,12,0,22}, + {44,40,28,32,45,23,36,35,29,27,38,43,26,30,34,31,41,37,33,25,42,24,39,10,13,18,0,9,12,1,4,15,20,22,14,7,6,17,19,5,8,16,3,11,2,21}, + {32,30,27,36,41,25,43,31,33,42,44,23,39,35,24,34,38,40,37,45,28,29,26,1,19,8,15,9,16,13,18,14,12,2,22,21,6,11,20,0,5,4,7,17,10,3}, + {44,30,43,34,38,41,37,25,24,32,36,28,35,39,33,26,40,27,29,42,31,45,23,0,22,2,14,10,19,16,8,18,3,12,11,9,4,17,6,5,7,21,20,13,15,1}, + {37,45,31,27,25,44,43,41,34,28,36,30,26,38,35,29,39,33,42,32,23,24,40,13,5,7,17,0,9,16,21,10,3,18,14,19,11,8,2,12,4,22,15,6,20,1}, + {33,24,23,36,29,27,42,40,45,43,35,44,30,38,41,37,34,26,25,32,28,39,31,22,10,0,4,20,12,3,21,5,17,14,1,8,13,7,2,18,6,11,16,15,9,19}, + {41,27,40,31,37,29,36,26,25,39,38,33,23,32,42,28,44,35,34,45,24,30,43,22,16,8,17,5,13,2,6,11,3,1,15,21,10,12,19,0,20,9,7,4,18,14}, + {39,43,42,29,41,35,32,34,33,31,23,28,36,26,40,38,37,25,44,27,30,45,24,10,2,18,1,16,22,9,12,19,7,21,14,3,13,11,8,4,15,5,20,6,17,0}, + {38,42,25,41,29,34,24,32,43,26,39,28,30,44,37,36,45,40,31,27,23,33,35,14,5,9,6,16,10,13,17,1,12,20,0,15,7,18,2,8,4,19,22,11,3,21}, + {32,28,45,42,30,44,26,40,24,41,29,39,31,33,36,27,25,34,38,35,37,43,23,20,15,3,13,16,5,18,21,22,10,4,12,0,6,1,8,2,14,7,17,19,11,9}, + {44,25,36,38,35,34,45,27,43,40,23,28,24,37,31,42,32,39,29,41,33,30,26,21,1,17,9,12,16,2,11,4,19,3,14,6,18,10,0,5,8,7,20,13,22,15}, + {44,41,36,40,38,25,34,32,45,30,24,37,42,29,31,35,28,26,43,33,39,27,23,16,15,11,0,20,9,6,10,12,18,4,19,13,2,7,14,22,1,8,17,5,21,3}, + {28,33,43,38,30,27,39,36,40,25,42,29,26,45,32,23,34,24,44,37,31,41,35,6,1,21,20,15,18,13,17,22,3,11,16,12,9,0,2,5,8,7,10,14,19,4}, + {39,45,42,23,30,43,31,28,40,26,33,37,24,32,41,36,38,29,44,34,27,25,35,15,8,1,5,12,17,11,6,19,10,14,2,20,3,16,18,4,21,0,22,13,9,7}, + {45,39,23,30,42,31,29,35,43,27,38,40,37,34,36,44,41,26,32,24,33,25,28,2,11,18,17,9,0,6,8,5,10,3,21,7,14,4,13,1,16,22,12,20,19,15}, + {41,33,45,42,28,31,27,38,29,26,25,24,32,40,34,44,37,30,43,35,39,36,23,19,13,5,18,2,12,3,7,15,14,0,20,6,22,11,21,4,10,9,8,1,17,16}, + {39,24,31,26,37,23,35,34,41,30,28,42,44,38,45,40,27,43,29,32,36,33,25,4,9,15,5,0,10,2,7,12,8,3,18,17,6,20,22,19,13,16,14,21,1,11}, + {44,37,28,31,27,40,42,24,30,45,39,38,34,43,32,23,36,26,35,41,33,25,29,14,18,2,8,12,17,20,19,16,11,15,3,1,13,7,6,4,9,22,0,10,21,5}, + {38,27,42,33,26,35,32,34,25,28,43,23,45,39,31,37,24,44,30,41,40,36,29,12,0,3,17,10,7,18,8,22,21,19,5,14,16,20,2,11,9,13,1,6,4,15}, + {37,35,28,24,33,42,31,25,45,32,27,41,44,38,40,26,36,34,29,39,30,43,23,3,22,17,14,9,16,4,6,10,19,21,11,2,13,15,7,5,20,12,1,18,8,0}, + {45,36,30,34,32,24,42,23,26,29,25,44,41,40,33,31,38,37,35,39,43,28,27,0,17,4,7,10,3,1,8,16,21,13,15,9,18,6,12,22,14,2,11,19,5,20}, + {41,24,30,43,35,39,26,31,23,33,42,28,25,40,29,44,38,27,36,32,34,37,45,4,2,20,6,9,16,15,14,18,11,3,13,19,5,7,10,8,17,1,12,22,0,21}, + {43,40,28,34,24,39,25,45,27,33,38,37,42,29,31,44,32,30,36,41,26,35,23,15,19,13,21,14,10,18,0,2,8,22,12,7,6,5,20,11,4,9,3,17,16,1}, + {36,41,39,24,32,45,28,43,26,38,27,25,30,34,37,31,29,35,44,23,42,40,33,15,20,8,7,14,3,19,2,13,6,21,11,9,1,22,0,4,17,5,18,12,10,16}, + {43,25,39,30,34,40,44,41,23,28,38,42,32,37,27,33,36,29,35,45,26,24,31,4,22,1,12,17,20,14,9,18,11,10,8,0,7,5,16,2,21,13,3,6,15,19}, + {25,23,43,32,26,33,44,24,35,42,30,40,27,38,34,28,36,45,31,39,37,29,41,22,1,8,17,15,20,13,3,2,18,12,5,14,7,11,9,6,0,4,16,19,10,21}, + {23,42,40,26,32,31,33,36,30,35,43,27,39,29,44,25,28,38,24,45,41,34,37,12,21,22,1,20,13,7,9,4,11,5,17,15,0,6,10,3,14,18,16,2,8,19}, + {24,23,38,41,35,26,45,37,27,34,42,40,36,30,43,25,33,28,44,39,32,29,31,21,11,15,19,9,4,12,8,5,17,2,6,3,13,22,16,1,7,18,14,20,10,0}, + {42,24,33,31,26,28,45,40,44,25,23,39,34,27,38,30,37,43,32,35,29,41,36,22,1,16,4,12,10,2,11,20,15,3,5,7,0,6,18,14,19,13,21,17,9,8}, + {44,23,34,33,42,30,43,32,40,35,31,39,29,41,36,24,27,37,28,38,25,45,26,21,16,2,8,3,7,12,11,14,13,19,1,0,20,10,18,9,15,5,17,22,6,4}, + {35,32,25,41,29,31,36,24,28,26,39,23,42,37,33,27,44,43,34,38,40,45,30,7,9,13,10,19,4,2,20,1,6,16,8,17,12,15,18,3,0,5,11,22,14,21}, + {31,45,24,33,38,44,28,34,42,40,25,35,39,23,29,41,43,32,26,30,36,27,37,15,13,12,5,0,7,18,21,4,8,20,10,1,3,6,2,17,14,19,16,22,11,9}, + {23,33,26,45,42,34,25,31,44,43,29,40,36,27,37,30,41,35,39,32,28,24,38,22,14,2,21,17,20,7,13,6,0,9,3,8,5,11,15,10,12,18,1,16,19,4}, + {31,33,39,37,23,32,24,30,42,44,38,27,36,34,25,29,40,28,35,41,45,43,26,13,19,0,8,1,15,3,7,18,5,14,20,9,2,10,4,11,21,22,12,6,17,16}, + {33,31,37,32,24,23,44,26,39,27,43,42,29,28,40,35,25,45,36,30,38,41,34,17,0,8,15,3,18,10,13,12,4,6,2,20,1,14,11,9,5,16,7,21,19,22}, + {45,30,37,27,29,33,41,32,39,26,38,28,40,34,42,24,23,25,44,35,31,36,43,0,15,17,9,20,21,4,19,13,3,5,18,8,14,11,10,22,12,6,16,1,7,2}, + {25,23,30,43,32,42,27,26,45,33,41,38,36,35,28,39,44,37,29,31,40,24,34,20,22,18,21,16,12,11,15,17,4,9,1,19,3,5,13,0,8,14,7,10,2,6}, + {29,33,42,27,23,41,26,32,44,39,38,40,43,31,30,28,25,35,24,37,34,45,36,22,7,20,19,21,8,12,0,18,16,13,3,5,11,2,10,15,4,1,14,6,17,9}, + {35,42,40,24,32,31,26,23,44,27,45,30,38,36,28,34,43,37,25,39,41,33,29,4,13,0,21,6,18,16,7,20,22,3,2,5,11,10,19,1,15,9,17,14,8,12}, + {39,44,23,43,36,34,32,42,30,45,29,26,35,28,41,37,24,27,40,38,25,33,31,22,8,7,15,13,3,12,5,16,20,0,17,21,11,14,6,9,18,4,1,10,2,19}, + {26,24,33,38,29,44,27,25,34,23,32,42,35,28,37,36,41,30,45,43,40,39,31,13,1,4,10,20,8,19,16,22,7,6,21,17,0,12,14,9,11,5,3,15,2,18}, + {25,24,36,29,27,26,37,44,30,28,41,33,31,42,45,39,23,40,35,34,32,43,38,7,0,2,6,9,21,19,5,11,4,22,12,1,17,15,18,14,8,3,10,20,16,13}, + {42,45,25,37,39,32,23,33,28,27,44,29,24,30,34,38,35,40,43,26,36,31,41,10,3,21,19,4,6,13,22,9,2,5,14,16,20,15,1,17,8,7,18,12,0,11}, + {41,40,39,30,37,31,23,45,36,38,29,35,43,33,26,32,44,25,34,27,42,28,24,0,9,5,3,11,17,1,13,12,7,21,16,14,19,2,6,20,8,10,15,22,4,18}, + {33,23,35,32,30,45,31,24,27,40,26,39,28,38,29,42,25,41,36,34,44,43,37,10,3,1,7,20,17,5,18,16,11,6,15,12,8,22,21,19,2,14,13,0,4,9}, + {36,28,43,25,41,30,24,27,40,45,32,37,35,34,38,29,26,44,33,31,39,42,23,10,21,19,2,16,20,13,5,17,11,7,0,22,9,15,1,12,14,18,3,6,8,4}, + {28,42,36,23,27,37,44,43,45,33,29,32,39,30,34,31,26,38,35,41,24,40,25,21,14,9,11,6,18,8,1,3,16,2,22,5,20,12,15,0,10,4,13,7,19,17}, + {39,37,41,23,25,30,35,44,38,36,43,24,26,29,31,27,40,33,42,28,45,34,32,7,0,13,20,2,10,5,21,4,9,6,12,15,8,16,14,11,19,18,1,3,22,17}, + {28,43,32,39,33,42,37,36,44,31,40,27,25,30,29,24,23,35,38,34,45,41,26,11,5,1,0,6,13,15,20,19,9,7,12,22,17,4,16,2,21,8,10,18,14,3}, + {38,43,45,42,40,34,23,39,31,25,27,37,35,30,44,29,33,32,24,26,36,28,41,20,11,17,10,3,0,15,12,22,13,6,19,2,16,21,4,1,18,14,8,7,9,5}, + {25,38,45,27,30,39,43,29,36,40,42,24,32,28,31,37,44,41,35,26,34,33,23,21,16,7,4,10,9,22,15,18,2,19,1,20,3,8,13,6,5,0,12,14,11,17}, + {30,38,24,41,32,42,34,29,43,33,40,44,35,37,39,25,31,45,26,28,27,36,23,21,11,1,0,22,8,6,2,9,12,4,3,14,10,15,20,13,7,19,17,16,18,5}, + {36,27,31,44,43,33,28,24,26,29,35,42,25,34,41,39,30,37,45,32,38,40,23,0,17,12,5,4,9,7,16,19,2,8,10,6,20,22,21,15,18,1,13,3,14,11}, + {30,26,44,40,25,43,39,45,35,37,41,34,32,38,28,31,36,24,27,29,33,42,23,22,21,20,8,17,19,13,3,10,2,15,12,18,1,11,16,14,5,7,6,4,0,9}, + {34,28,23,36,41,45,38,37,27,40,42,35,25,44,31,26,29,43,33,24,32,39,30,15,12,11,7,16,8,10,13,18,21,3,17,6,14,19,9,0,5,20,1,22,4,2}, + {44,30,34,33,31,42,26,25,43,40,38,32,45,35,41,37,39,24,36,27,23,29,28,19,8,15,9,0,17,10,2,4,21,3,13,20,6,7,14,16,18,12,5,22,1,11}, + {37,45,24,32,39,28,40,30,44,29,42,25,35,38,36,43,26,41,33,27,31,34,23,10,22,7,20,19,14,6,0,13,4,18,21,12,5,15,1,3,17,9,8,11,2,16}, + {31,27,41,24,43,35,44,33,26,39,30,32,38,29,34,42,37,40,25,36,28,45,23,7,9,14,20,6,19,13,16,22,15,17,4,8,11,0,21,5,18,2,1,10,12,3}, + {30,34,45,37,41,24,28,36,25,44,40,43,27,32,35,33,23,31,38,26,39,29,42,17,19,11,15,22,3,21,0,4,1,5,18,14,20,8,6,13,10,16,12,7,9,2}, + {40,43,29,44,35,23,42,24,37,27,34,26,25,39,38,28,41,45,30,32,36,33,31,5,17,0,16,8,6,4,9,11,19,22,21,7,12,20,2,18,3,14,10,1,15,13}, + {27,29,44,30,45,31,41,37,32,39,34,28,43,38,36,26,40,24,23,33,35,42,25,16,5,3,19,18,11,15,17,13,7,6,20,4,8,10,2,1,14,22,0,21,9,12}, + {34,27,25,45,31,24,28,40,23,37,41,29,43,39,32,33,30,44,26,42,36,38,35,20,13,11,3,1,16,0,10,12,8,15,19,5,17,7,21,4,2,6,9,22,14,18}, + {26,43,32,25,33,38,24,29,23,31,34,27,45,35,37,42,40,44,36,41,28,39,30,6,9,2,19,17,11,0,16,5,22,10,4,14,7,18,3,15,12,21,13,20,1,8}, + {31,33,26,45,23,30,37,27,41,40,28,39,42,32,34,36,25,43,38,35,24,29,44,14,6,5,7,12,20,15,9,13,4,1,3,19,21,2,22,11,16,18,0,10,17,8}, + {43,36,44,42,23,34,38,29,26,39,25,30,35,28,37,40,33,31,41,32,45,27,24,22,3,20,14,6,0,11,19,1,10,15,5,21,17,7,4,12,9,8,16,13,18,2}, + {43,32,34,25,28,44,27,33,41,29,42,23,35,37,24,40,45,30,26,36,38,31,39,15,12,4,19,16,10,2,11,1,21,0,20,5,14,13,9,22,3,6,17,8,18,7}, + {45,32,29,40,37,42,44,35,38,30,39,31,26,43,27,24,41,34,28,25,33,36,23,2,1,7,12,22,0,4,17,5,8,21,16,20,13,15,11,18,14,19,10,6,3,9}, + {27,44,38,32,35,29,26,40,42,24,41,37,23,25,34,39,45,33,30,28,36,31,43,21,4,17,1,15,11,20,14,10,12,18,6,22,5,3,19,9,0,2,8,7,13,16}, + {38,28,24,37,42,40,36,31,23,27,45,29,41,25,35,26,33,30,44,43,32,39,34,22,4,19,6,9,17,11,3,1,8,12,5,14,10,21,0,2,20,16,7,15,13,18}, + {34,27,31,39,24,29,44,38,45,23,41,35,28,30,32,36,26,42,25,33,37,40,43,17,18,22,7,13,6,14,1,8,11,5,4,16,21,9,20,10,12,0,3,19,15,2}, + {25,33,44,34,38,43,41,29,35,31,39,23,27,45,30,26,42,32,40,37,36,24,28,15,17,12,4,2,11,6,22,18,14,9,21,10,5,20,3,1,7,13,0,19,16,8}, + {43,45,37,40,39,36,27,30,25,44,42,28,34,26,32,24,38,31,35,41,23,33,29,13,7,5,3,12,14,18,8,1,6,20,2,15,4,19,0,17,10,16,21,11,22,9}, + {31,28,45,33,25,44,34,30,32,24,35,27,40,39,43,29,42,38,36,26,41,37,23,11,6,1,3,0,14,17,22,18,21,16,10,8,19,20,15,4,7,13,12,9,5,2}, + {24,43,33,26,39,38,27,23,40,34,28,31,25,37,45,35,29,36,32,42,30,41,44,22,18,2,21,20,10,1,16,6,19,9,0,15,17,8,13,4,7,11,14,12,3,5}, + {25,24,28,23,36,29,45,31,26,44,43,33,30,38,42,32,35,27,39,34,37,41,40,21,5,9,16,2,17,0,12,8,18,15,19,6,11,4,1,7,10,3,14,13,20,22}, + {30,26,42,31,40,45,27,41,25,44,38,35,34,29,32,39,33,23,37,28,43,36,24,11,3,2,7,14,6,0,4,20,22,1,17,8,15,12,18,5,10,13,9,21,16,19}, + {37,25,28,36,43,23,32,40,31,45,39,29,44,41,27,33,30,35,38,42,34,24,26,20,2,7,16,15,3,22,8,17,11,6,4,18,9,13,0,14,5,10,19,12,21,1}, + {26,33,43,27,34,45,37,39,29,42,30,36,28,31,38,41,25,40,44,35,24,32,23,9,12,3,21,18,2,11,1,8,13,15,10,22,20,17,14,7,6,4,19,0,16,5}, + {23,30,43,37,25,40,42,35,34,27,26,31,29,38,33,41,36,28,32,24,45,44,39,20,9,14,5,15,19,1,18,7,17,11,8,4,2,12,21,3,0,22,16,13,6,10}, + {33,32,26,44,34,24,31,42,37,36,30,38,45,39,28,27,43,29,41,35,25,40,23,16,4,13,11,21,1,10,3,15,7,2,14,8,22,5,9,12,6,19,0,17,20,18}, + {23,35,45,43,34,37,27,31,41,28,42,38,32,36,39,30,24,33,25,40,29,44,26,9,0,7,22,15,18,13,16,5,11,2,12,17,3,14,1,6,4,19,8,21,20,10}, + {29,44,39,36,34,26,45,35,32,42,30,27,41,24,38,40,43,33,28,23,31,37,25,9,14,10,22,17,6,8,20,15,19,16,2,13,1,7,4,18,12,21,3,0,11,5}, + {23,35,28,27,44,29,25,30,34,41,40,36,42,39,43,37,32,45,31,38,24,26,33,8,11,17,2,5,14,19,10,13,7,4,20,9,15,18,3,6,0,16,22,1,12,21}, + {35,40,43,34,27,29,32,23,33,31,39,38,45,30,25,24,36,26,44,28,42,37,41,15,9,7,20,5,11,10,22,14,21,3,16,18,4,2,1,17,6,0,12,19,8,13}, + {23,35,31,42,41,39,43,24,28,25,38,29,45,33,44,27,37,40,30,36,32,26,34,17,10,7,6,13,21,11,5,9,4,19,16,3,18,14,8,1,22,12,15,20,2,0}, + {35,38,29,24,43,45,23,34,28,40,31,39,30,33,36,41,44,42,37,32,27,26,25,9,11,8,21,16,22,5,12,7,19,20,10,0,14,18,1,3,6,15,17,4,13,2}, + {24,40,33,27,30,23,39,36,43,26,37,29,32,25,38,35,31,44,42,34,41,45,28,1,10,19,3,16,4,7,9,6,21,15,22,13,18,12,2,5,11,0,14,8,20,17}, + {23,34,43,33,30,37,29,35,27,31,32,42,41,44,28,26,38,45,40,25,39,36,24,0,19,1,21,11,4,15,7,9,20,3,22,2,16,14,13,10,18,12,6,5,17,8}, + {33,28,40,30,39,31,44,38,45,35,23,26,25,29,24,41,32,27,37,43,36,42,34,11,7,6,15,0,14,8,19,13,22,18,5,9,12,4,2,21,17,10,3,1,20,16}, + {43,35,33,31,41,25,44,37,39,29,27,32,24,28,45,40,26,36,38,30,42,34,23,9,21,11,22,1,20,14,16,6,19,13,0,3,7,15,12,18,5,4,17,10,8,2}, + {42,24,28,41,40,37,33,23,36,44,27,29,31,34,26,43,30,32,25,35,39,38,45,22,1,9,11,21,6,0,16,12,17,2,7,4,8,10,3,20,19,13,18,15,5,14}, + {39,26,41,23,45,36,40,35,43,33,31,29,28,44,38,37,30,34,27,32,42,25,24,15,20,6,16,7,19,14,0,11,2,22,10,4,8,18,13,1,3,21,17,12,9,5}, + {27,31,34,43,23,37,26,30,41,35,39,25,44,32,28,24,33,40,42,36,29,45,38,20,9,8,14,17,2,16,13,18,4,21,1,12,15,11,0,6,5,7,19,22,10,3}, + {36,27,31,25,34,24,35,37,43,29,42,45,40,38,30,32,26,44,33,39,28,41,23,14,13,8,16,7,20,17,10,4,12,5,3,19,22,15,0,18,21,1,6,2,11,9}, + {25,27,33,45,29,38,28,34,40,24,30,26,37,36,32,43,35,41,44,42,31,39,23,9,5,0,22,13,18,14,19,2,4,3,21,16,1,11,17,15,10,12,8,7,6,20}, + {24,40,36,39,27,31,29,32,25,44,38,35,37,28,23,30,45,41,26,42,34,43,33,18,12,8,17,21,1,6,5,11,9,16,13,15,20,19,10,2,22,3,0,4,7,14}, + {36,30,38,34,27,42,32,29,35,33,28,45,39,25,44,43,41,26,23,31,24,37,40,17,0,20,3,11,7,5,15,19,21,14,22,2,1,4,8,10,12,16,18,9,13,6}, + {37,32,26,45,43,38,44,27,35,40,39,29,28,34,42,25,23,36,31,41,33,30,24,12,2,22,10,7,15,4,20,14,9,8,6,1,11,18,0,5,3,17,16,13,21,19}, + {43,36,44,38,26,24,35,41,27,40,23,33,45,30,28,37,39,29,34,31,25,42,32,0,18,1,7,22,8,11,20,13,4,17,16,6,10,15,2,5,9,12,19,14,21,3}, + } + }, + { 47, { + {46,34,41,35,25,24,32,44,31,29,38,36,42,45,33,37,40,43,26,30,39,27,28,5,23,11,13,8,0,22,21,14,9,3,20,12,4,7,17,1,15,10,6,19,18,2,16}, + {26,34,23,44,29,28,35,46,36,31,40,42,24,41,25,33,30,39,32,38,45,27,37,43,6,14,17,21,5,8,16,22,18,1,4,9,7,19,12,0,10,13,11,20,3,2,15}, + {29,44,35,23,38,37,46,24,32,31,36,28,25,43,27,39,42,45,41,34,40,26,33,30,20,11,10,0,8,15,2,5,13,9,18,17,14,12,4,16,3,7,21,22,6,1,19}, + {29,45,34,44,31,39,41,28,32,25,35,26,30,37,27,46,43,36,33,40,38,24,42,21,0,2,17,15,11,22,8,20,19,4,6,13,1,14,10,5,12,18,7,9,23,3,16}, + {40,29,27,30,35,37,39,41,31,43,42,24,36,46,33,28,45,25,34,44,26,38,32,22,12,3,8,2,7,1,15,9,11,13,17,19,21,23,6,4,10,14,20,16,18,0,5}, + {40,28,46,29,37,42,24,36,33,25,27,35,30,32,39,43,26,38,41,44,31,34,45,5,1,13,22,15,14,6,17,16,21,23,20,18,4,0,2,19,9,12,8,10,7,3,11}, + {25,32,28,31,35,45,38,37,40,36,42,41,34,26,24,46,33,44,39,27,30,43,29,18,9,8,7,10,17,13,15,19,11,16,14,23,5,21,12,6,3,1,0,2,22,4,20}, + {39,37,26,25,42,24,38,36,44,28,34,31,45,30,29,33,40,32,43,41,27,35,46,2,16,6,15,1,23,17,8,3,10,0,7,4,11,21,13,9,20,18,12,14,22,19,5}, + {32,34,33,39,37,41,27,45,43,24,46,44,28,40,29,38,25,35,30,26,42,31,36,21,11,1,19,18,2,12,22,9,7,14,17,20,0,6,15,13,16,5,10,4,23,8,3}, + {27,40,29,37,33,38,46,43,39,26,25,35,31,30,45,32,34,24,44,42,28,36,41,1,12,10,23,20,13,17,16,6,22,3,7,11,2,8,15,5,21,18,9,0,4,19,14}, + {39,41,43,42,33,30,45,34,36,25,29,40,24,44,28,26,38,32,31,37,35,27,23,46,12,0,20,2,8,10,13,5,9,16,7,14,22,3,6,17,11,15,19,18,4,1,21}, + {27,40,31,26,33,25,46,30,35,32,28,45,44,37,42,29,34,41,39,38,24,36,43,21,3,0,11,13,15,10,20,8,1,12,23,4,22,5,2,7,17,16,6,14,18,9,19}, + {24,29,45,37,32,25,43,30,41,39,34,46,36,38,31,42,44,26,40,35,28,33,27,9,23,22,11,0,20,7,16,21,19,17,3,18,8,14,1,10,2,13,6,12,4,15,5}, + {29,27,46,44,26,36,32,42,35,41,38,28,37,34,25,33,45,39,31,30,40,43,23,24,3,9,0,17,6,11,8,18,1,22,20,7,2,16,19,10,13,15,21,4,12,5,14}, + {28,34,46,39,23,35,43,45,36,24,44,25,38,37,33,27,26,32,40,30,29,42,41,31,11,16,3,5,1,8,22,20,2,9,13,15,14,4,21,0,6,18,12,17,7,19,10}, + {40,23,41,29,31,28,26,34,36,27,25,46,43,38,44,32,45,42,39,33,37,35,30,24,7,16,6,2,14,18,10,4,11,13,19,3,12,0,5,20,22,21,1,17,8,15,9}, + {39,33,38,45,37,36,35,24,44,31,30,43,34,26,28,40,29,32,25,27,46,42,41,7,20,14,0,22,11,10,21,5,19,13,6,3,9,4,2,18,1,17,12,23,16,15,8}, + {31,24,25,28,41,27,42,39,38,44,30,40,45,36,32,29,34,33,37,46,43,35,26,10,16,7,1,17,22,9,6,12,15,21,18,14,2,4,23,20,5,19,13,11,8,0,3}, + {29,33,45,38,34,44,27,32,39,31,43,36,41,46,24,35,37,26,28,42,30,23,40,25,12,2,6,19,0,20,13,10,1,3,18,5,11,4,15,22,14,8,7,16,21,17,9}, + {27,37,43,29,32,26,36,45,40,31,38,33,42,34,30,35,28,41,25,23,39,44,46,24,11,18,13,0,16,3,2,9,21,7,15,10,12,14,19,5,6,17,4,20,8,1,22}, + {40,26,34,44,39,23,36,35,31,42,27,43,28,33,38,46,25,32,41,29,24,45,37,30,12,22,3,10,0,19,6,4,17,9,15,2,20,5,11,8,16,18,13,7,21,1,14}, + {39,44,25,41,45,35,42,24,36,34,27,46,38,30,23,32,26,29,37,40,28,33,43,31,16,12,1,11,14,0,13,21,4,3,9,5,20,18,10,6,2,7,22,8,17,15,19}, + {29,42,34,45,32,35,41,23,28,43,40,36,39,38,30,46,27,31,33,37,25,44,26,24,22,8,3,14,5,7,18,12,1,21,20,2,19,11,13,15,6,9,16,0,4,17,10}, + {43,26,24,30,27,42,38,23,35,33,31,40,28,36,34,32,29,45,41,25,44,37,39,46,2,22,1,4,3,8,17,10,20,9,14,21,15,0,6,11,7,18,13,12,5,16,19}, + {26,43,28,35,30,33,25,31,24,38,44,34,29,37,46,27,42,45,41,40,32,39,36,14,23,16,18,8,4,7,9,6,13,12,22,21,5,10,1,3,11,20,15,19,17,2,0}, + {23,37,43,34,29,28,32,45,42,39,27,33,36,24,46,38,35,41,31,25,40,26,44,30,3,22,10,13,17,8,18,9,14,4,12,11,6,0,15,1,20,19,21,16,5,2,7}, + {40,29,42,38,23,25,34,27,30,41,28,45,39,32,37,33,46,43,31,35,26,44,24,36,10,20,12,6,5,22,9,3,18,8,17,13,21,7,19,4,1,14,0,16,2,15,11}, + {35,25,38,31,37,29,26,36,34,41,32,42,45,33,24,28,46,27,30,44,39,43,40,22,12,14,10,18,13,17,19,5,15,23,2,20,6,21,16,4,9,11,7,3,0,8,1}, + {39,30,41,31,36,43,29,27,44,42,24,38,40,46,28,33,26,45,37,32,35,25,23,34,20,8,6,2,9,17,21,0,22,14,4,10,18,7,13,1,15,3,19,16,12,5,11}, + {24,35,44,37,29,40,34,32,31,41,30,42,36,25,46,39,38,33,26,28,43,45,27,21,1,15,19,10,12,4,3,5,23,14,6,20,9,13,16,7,22,2,11,0,18,17,8}, + {29,40,36,42,38,37,45,32,26,46,43,34,39,24,31,44,30,27,41,25,28,35,33,11,18,9,16,12,6,13,19,2,14,5,21,4,15,8,17,20,22,0,3,7,10,23,1}, + {41,27,37,31,36,34,26,38,45,29,44,32,30,35,46,39,33,25,40,28,43,42,24,8,19,7,14,21,2,12,15,13,22,9,17,4,3,5,1,23,18,11,16,20,6,0,10}, + {46,29,26,36,30,39,35,33,24,31,27,41,34,28,40,42,37,43,38,45,44,25,32,6,4,0,19,17,16,1,22,10,7,20,18,9,15,8,21,5,13,2,11,3,14,23,12}, + {32,42,44,39,30,34,28,33,29,36,27,38,35,25,46,31,45,43,37,40,26,41,23,24,0,9,2,16,7,18,3,5,14,20,4,19,15,12,11,22,10,13,21,6,1,8,17}, + {44,27,38,37,41,34,42,31,26,30,43,24,35,29,39,32,28,46,40,23,25,45,33,36,6,8,17,15,2,16,9,7,4,14,5,18,22,3,19,13,0,12,21,20,11,1,10}, + {39,41,37,43,34,31,26,38,30,27,33,23,35,28,42,40,25,32,29,36,44,46,45,24,7,4,6,5,8,12,14,0,17,10,19,18,13,2,20,15,22,21,16,11,9,1,3}, + {25,43,41,38,44,33,42,28,39,45,40,46,27,31,34,32,36,30,24,23,29,37,35,26,1,6,7,12,8,21,9,13,15,5,0,19,16,2,20,17,11,10,14,18,4,22,3}, + {25,27,36,33,32,34,38,46,43,40,24,29,41,28,31,42,26,37,30,39,44,35,45,18,23,9,16,3,0,12,21,15,17,14,1,8,6,10,4,13,19,2,22,20,5,11,7}, + {24,35,29,28,42,38,43,25,46,26,32,41,37,36,27,44,33,31,45,30,40,39,34,21,8,14,7,2,16,4,1,11,19,22,3,0,13,5,10,15,23,17,6,20,12,9,18}, + {30,45,43,46,31,39,33,25,42,35,41,37,28,44,38,34,27,23,29,40,26,24,32,36,11,2,10,0,4,17,3,5,7,15,12,19,6,18,22,1,9,20,8,13,21,14,16}, + {30,25,34,39,26,29,37,43,36,42,31,46,24,44,38,40,28,35,27,45,32,41,33,5,4,15,11,16,10,22,2,21,3,20,23,13,9,6,19,12,7,14,18,1,17,8,0}, + {27,23,39,35,33,44,29,36,42,26,45,25,40,34,37,30,43,41,28,38,46,32,24,31,21,19,15,7,22,14,6,5,20,9,13,3,8,10,0,2,4,17,18,1,16,12,11}, + {38,42,45,39,27,40,33,36,29,46,43,41,44,30,25,34,26,37,24,35,31,28,32,10,18,3,16,4,17,5,22,20,9,6,15,19,7,21,0,2,8,13,1,23,12,11,14}, + {24,41,40,42,25,36,29,32,39,45,31,37,44,28,27,33,46,30,43,38,35,26,23,34,0,22,9,19,13,6,17,8,20,15,4,11,1,3,16,18,10,21,7,2,12,5,14}, + {31,45,40,24,39,43,30,34,38,46,23,26,37,33,29,32,42,27,35,41,25,36,44,28,5,3,11,6,20,12,8,2,18,9,17,0,13,7,22,21,16,19,15,4,10,1,14}, + {27,42,23,45,37,31,25,36,33,44,34,43,32,30,26,41,29,28,35,38,46,40,39,24,8,6,2,5,3,10,22,9,4,17,12,14,19,11,16,15,13,21,1,0,7,20,18}, + {34,25,42,27,45,40,26,38,43,35,33,37,44,30,46,28,24,36,32,29,39,41,31,15,20,1,18,0,3,7,4,21,19,8,22,14,6,10,23,16,5,13,2,17,12,11,9}, + {29,41,38,32,44,42,27,26,28,24,36,34,37,39,35,33,45,31,40,30,46,25,43,9,17,12,20,4,13,15,10,23,11,7,18,21,3,8,6,1,0,14,2,22,5,16,19}, + {24,32,27,37,39,29,43,42,30,41,46,33,35,26,45,31,40,34,28,36,44,38,23,25,1,17,14,12,10,16,15,13,11,9,18,22,2,0,5,20,3,19,21,7,6,8,4}, + {37,26,35,27,45,24,36,42,44,29,46,25,40,34,28,33,39,43,32,30,41,23,38,31,21,18,9,6,13,2,0,8,12,19,17,14,22,5,20,7,1,10,16,11,15,4,3}, + {42,32,29,40,38,41,46,45,34,26,35,31,25,27,37,30,43,39,36,44,28,23,33,24,7,21,15,14,18,22,20,3,5,12,9,1,0,8,16,6,4,19,11,10,13,2,17}, + {23,35,45,36,41,43,28,40,33,24,37,29,34,31,39,38,30,44,27,42,32,46,26,25,11,6,15,18,22,13,21,1,5,17,4,0,3,9,19,2,20,8,7,14,10,16,12}, + {30,26,36,41,33,43,38,46,28,40,34,42,25,31,44,35,39,24,27,29,32,45,37,21,17,8,1,16,6,0,23,7,13,15,10,18,12,11,14,2,5,4,22,19,9,20,3}, + {24,40,26,43,36,33,25,27,31,44,23,46,41,37,30,42,32,45,34,28,38,29,35,39,12,15,11,2,20,16,14,7,10,22,1,8,19,5,13,21,18,9,4,3,0,17,6}, + {36,30,29,32,25,37,43,39,44,23,45,27,33,41,38,46,34,40,35,31,26,28,42,24,18,2,19,4,22,0,15,9,21,1,13,10,3,17,16,5,7,20,8,12,6,14,11}, + {24,36,42,29,35,28,44,46,25,38,45,39,23,40,43,32,30,41,26,33,27,34,37,31,19,8,6,4,7,3,16,5,0,9,18,11,10,12,22,20,15,17,2,21,14,1,13}, + {24,40,27,34,44,31,45,42,32,26,23,33,29,36,25,41,46,38,35,37,28,43,39,30,0,5,9,15,13,3,22,14,8,11,17,2,16,19,6,10,1,18,7,21,4,12,20}, + {43,33,39,34,38,24,29,45,40,44,35,31,41,28,37,42,26,25,36,32,46,30,27,22,12,16,2,19,8,11,9,17,20,5,1,6,23,0,18,21,3,14,7,10,4,13,15}, + {27,34,36,30,26,37,25,32,29,39,33,28,38,46,44,40,31,24,43,45,35,42,41,21,19,0,11,22,5,18,16,6,2,20,8,17,4,15,23,12,3,9,13,7,14,1,10}, + {37,46,24,33,44,35,23,36,31,29,45,27,39,43,32,28,30,38,42,26,40,34,25,41,10,9,22,21,15,8,11,7,6,0,4,13,1,14,2,17,5,20,3,16,18,12,19}, + {40,28,32,31,46,42,26,39,24,34,23,44,29,25,41,35,33,45,37,43,38,36,30,27,22,6,8,19,2,21,17,4,15,0,14,3,16,1,10,20,13,7,18,9,12,5,11}, + {36,29,26,42,27,35,25,45,24,37,28,41,23,33,43,30,34,44,46,32,40,38,31,39,19,22,20,2,10,1,17,16,14,11,8,15,0,6,21,12,9,7,4,3,5,13,18}, + {33,37,43,32,44,38,24,39,42,30,45,26,36,41,29,28,25,34,46,31,35,27,40,7,10,1,12,9,19,16,2,0,14,18,17,13,11,22,15,6,5,21,4,20,23,8,3}, + {32,40,45,29,39,28,46,42,25,35,30,44,34,38,36,27,43,41,24,26,31,37,33,22,21,23,6,18,14,9,4,8,17,3,16,15,10,13,11,19,5,7,0,2,20,1,12}, + {46,27,32,44,41,26,25,38,36,29,33,24,34,30,42,40,28,45,39,31,37,43,35,22,10,1,14,0,11,6,21,20,2,9,8,3,18,16,5,12,23,13,15,19,7,17,4}, + {41,44,38,34,30,43,31,36,28,46,24,26,39,33,27,25,29,35,40,45,32,37,42,0,23,9,19,22,16,15,7,6,13,17,14,8,4,20,5,12,18,11,1,3,21,10,2}, + {26,38,43,30,32,42,29,28,37,25,33,23,34,27,44,35,45,40,31,39,41,46,36,24,16,7,17,9,18,11,21,0,8,14,3,20,12,22,2,6,5,19,13,15,10,1,4}, + {25,43,35,44,32,28,24,45,42,40,39,27,31,41,37,36,29,46,38,30,33,26,34,21,17,0,15,12,11,14,10,2,19,9,4,22,16,7,13,23,3,1,6,8,18,20,5}, + {23,28,39,33,30,35,44,27,45,34,40,31,38,25,37,41,36,46,29,42,32,43,24,26,4,11,9,14,13,6,2,15,20,12,8,22,5,18,1,16,10,21,3,0,7,17,19}, + {40,37,35,27,43,41,36,33,39,29,24,31,28,26,45,42,44,30,34,46,32,38,25,20,3,1,17,5,0,10,9,14,23,15,11,19,7,12,8,2,13,4,16,18,22,6,21}, + {25,40,26,29,23,39,35,28,41,37,27,34,44,42,36,46,45,32,43,31,38,33,30,24,9,11,8,1,21,20,19,5,12,15,4,0,6,22,14,18,2,7,10,3,13,17,16}, + {31,39,42,26,32,41,28,24,43,35,25,38,45,30,40,46,27,33,44,37,29,36,34,17,23,10,4,1,11,20,15,13,5,9,16,14,21,6,2,19,8,18,22,0,3,12,7}, + {34,26,25,39,43,41,30,38,31,27,35,42,40,45,32,46,29,33,36,28,44,24,37,9,0,18,3,17,10,16,13,20,14,6,8,15,2,22,21,7,12,4,11,5,1,19,23}, + {33,23,46,31,35,44,29,25,41,45,37,27,40,42,39,30,32,26,36,34,28,43,38,24,21,4,10,9,13,2,1,5,7,12,17,22,0,8,20,16,11,14,6,18,15,3,19}, + {28,45,31,42,30,32,25,43,38,29,26,33,39,36,44,27,37,40,35,41,34,46,24,6,11,1,7,16,19,22,8,15,21,18,14,23,5,9,13,10,4,0,3,2,20,12,17}, + {35,39,42,25,43,45,36,26,32,29,37,24,44,30,38,33,46,27,40,31,41,28,23,34,11,4,7,20,18,17,16,19,21,15,1,13,6,10,14,22,2,9,8,3,12,5,0}, + {35,40,23,30,45,37,44,46,32,27,36,25,34,24,39,33,26,38,42,29,43,31,28,41,17,5,14,13,8,15,3,10,20,11,19,6,4,18,21,12,2,7,0,16,22,9,1}, + {43,26,35,25,31,39,41,37,28,42,33,36,44,32,38,30,46,29,27,45,34,40,24,20,15,6,9,11,2,19,4,16,22,5,17,13,10,21,7,23,3,12,1,8,18,0,14}, + {34,39,32,37,46,28,38,29,36,43,25,40,31,30,27,41,45,44,24,26,42,35,33,0,23,21,18,12,20,9,1,10,8,17,7,15,13,11,19,6,3,5,22,16,14,4,2}, + {25,32,31,38,34,26,45,39,36,44,41,40,43,35,24,46,30,42,29,33,28,37,27,22,8,11,20,9,13,1,7,2,6,23,15,17,3,21,14,16,5,19,0,12,10,18,4}, + {27,35,37,28,45,43,31,44,29,25,33,26,41,34,32,30,38,40,24,36,39,46,42,8,21,12,19,14,10,5,4,2,17,16,23,11,15,3,9,7,1,22,20,13,18,0,6}, + {41,46,33,31,39,26,37,28,40,25,38,34,36,35,45,44,27,42,32,30,43,29,24,22,12,5,7,14,19,11,3,9,16,15,17,4,20,23,6,1,8,18,10,21,0,13,2}, + {26,41,44,35,23,29,39,37,43,30,42,31,45,40,36,27,32,46,33,25,28,38,34,24,19,8,0,16,7,17,10,18,4,14,22,1,6,15,5,13,20,3,11,9,12,2,21}, + {41,28,37,36,42,38,45,23,25,32,46,35,43,31,26,39,33,30,29,44,34,27,40,24,7,19,20,5,9,8,12,0,2,21,10,16,18,3,6,17,14,11,4,1,15,13,22}, + {24,37,42,26,25,31,41,29,45,39,36,28,27,33,30,38,43,46,44,35,32,34,40,22,19,12,14,20,1,0,3,18,21,23,13,6,9,15,5,8,10,7,11,16,2,17,4}, + {32,35,33,45,37,43,36,25,28,40,42,31,44,24,27,38,30,39,46,26,34,41,29,15,8,13,20,12,23,18,14,4,1,16,0,5,22,19,10,7,9,3,11,2,21,6,17}, + {23,35,42,29,26,32,38,43,24,39,34,44,40,25,45,28,30,41,46,31,33,37,27,36,11,14,5,12,7,3,10,6,20,13,19,18,17,8,16,9,22,0,2,4,21,1,15}, + {42,26,24,36,45,39,43,34,32,27,41,44,30,38,29,40,33,46,37,23,28,25,31,35,16,21,9,0,8,13,2,4,18,5,7,17,12,15,10,20,1,22,3,19,14,6,11}, + {31,36,41,28,37,25,45,26,42,30,40,33,39,43,32,35,34,29,44,24,27,38,46,15,17,7,1,5,4,14,12,11,3,2,22,6,20,19,9,8,16,23,10,13,21,18,0}, + {29,46,34,25,45,26,41,36,38,43,35,33,32,39,27,44,37,28,31,30,24,40,42,15,10,3,17,8,0,22,13,4,6,23,12,1,18,20,14,19,16,2,5,9,7,11,21}, + {33,27,41,39,29,36,30,23,46,40,42,31,35,43,45,44,28,26,24,38,25,32,34,37,6,11,3,13,12,19,7,18,21,9,16,2,4,15,14,22,20,8,10,1,17,5,0}, + {37,44,35,45,33,43,41,25,32,27,26,24,34,30,29,46,39,28,38,36,31,40,23,42,11,5,4,10,1,21,12,0,20,14,22,9,8,6,13,3,16,15,2,19,17,7,18}, + {40,34,32,24,43,39,41,29,35,28,33,30,37,36,45,42,46,27,38,31,44,26,23,25,16,18,10,20,14,1,19,2,12,21,4,7,11,6,15,5,9,0,8,22,3,17,13}, + {25,24,40,27,33,31,41,43,37,34,26,42,36,46,35,38,30,45,29,44,39,28,32,4,16,15,22,7,12,5,10,18,20,13,9,11,23,8,14,2,19,6,0,3,1,17,21}, + {31,43,45,37,29,36,38,46,32,25,44,33,39,28,41,27,35,24,30,26,40,42,34,12,23,4,7,5,17,9,8,22,2,6,3,19,13,0,15,21,16,11,20,14,10,18,1}, + {41,24,26,44,40,46,34,37,29,27,35,42,33,31,36,43,38,45,32,28,23,39,30,25,4,17,2,19,9,0,5,10,13,8,12,15,6,3,18,11,21,16,7,22,1,20,14}, + {43,33,38,26,39,27,46,25,30,34,24,42,36,40,44,31,35,28,41,45,37,32,29,1,23,9,19,14,21,8,17,15,0,18,11,5,3,2,12,16,22,7,6,4,20,10,13}, + {40,27,39,41,44,30,28,38,37,35,42,31,26,36,34,45,24,33,46,32,29,43,25,16,15,6,11,1,13,22,5,18,9,7,0,19,2,8,17,23,14,12,10,21,4,20,3}, + {37,35,46,27,31,40,25,24,45,39,26,32,44,34,29,33,42,41,38,36,43,28,23,30,7,5,9,21,6,13,0,22,20,4,17,2,8,16,18,14,10,19,12,15,1,11,3}, + {32,26,24,29,38,36,34,27,41,31,30,43,33,35,45,37,28,42,25,40,46,39,44,21,23,13,19,0,18,5,15,9,16,4,7,11,8,6,3,12,10,2,1,17,14,22,20}, + } + }, + { 48, { + {33,29,38,37,42,35,25,36,28,26,39,32,34,24,43,31,45,41,40,46,30,27,44,47,4,21,15,12,16,10,19,23,17,11,14,7,1,5,22,2,20,8,3,18,9,13,0,6}, + {29,40,46,39,44,37,31,41,27,42,25,30,36,28,38,45,34,33,43,26,35,32,24,47,21,1,11,6,20,14,22,8,23,4,9,2,17,3,13,7,19,18,0,16,15,5,12,10}, + {35,34,29,36,26,31,44,33,39,28,43,46,25,30,24,41,32,47,27,37,40,45,38,42,7,18,9,22,21,3,11,17,14,0,10,5,20,2,12,23,6,19,13,16,8,4,1,15}, + {40,28,43,33,42,35,27,37,47,29,31,24,38,44,30,32,41,25,45,39,34,36,26,46,3,12,21,6,2,20,19,8,15,7,1,9,4,11,23,14,13,18,22,16,0,5,17,10}, + {24,45,26,34,28,31,47,42,39,33,46,29,38,41,30,37,36,35,44,25,43,32,40,27,7,14,20,11,1,9,8,5,16,15,13,21,6,22,4,19,18,0,3,23,17,10,12,2}, + {29,47,43,38,42,39,36,26,44,24,31,34,37,27,33,30,40,32,28,45,41,35,25,46,22,15,3,1,5,23,10,14,13,4,19,18,8,2,17,16,7,12,20,6,9,11,21,0}, + {33,29,27,32,43,30,47,24,34,26,28,46,40,45,44,35,38,31,39,41,37,25,42,36,23,10,22,19,1,15,6,12,14,21,3,17,20,13,16,11,18,9,7,2,4,8,0,5}, + {26,33,27,45,35,40,38,29,24,37,47,42,31,41,28,44,34,39,32,43,30,36,46,25,8,11,16,13,10,5,14,1,7,3,0,6,22,9,4,20,12,23,2,17,15,18,21,19}, + {45,47,35,44,29,31,26,28,46,39,25,32,36,41,27,42,34,43,37,33,24,30,38,40,2,4,12,14,13,11,8,18,5,0,9,7,15,17,22,21,10,1,3,16,6,19,23,20}, + {42,46,33,25,27,30,36,34,45,29,32,37,26,28,47,40,43,35,31,44,39,41,38,24,8,12,15,10,13,23,20,11,17,2,9,7,21,0,22,19,16,1,3,5,4,6,18,14}, + {38,35,47,41,26,34,30,27,33,31,29,44,32,40,25,37,39,43,42,46,36,45,28,24,13,23,10,5,16,0,4,20,3,15,7,22,21,2,11,9,18,17,8,1,14,12,6,19}, + {41,32,25,33,44,34,36,43,27,30,46,42,29,47,38,28,39,31,35,37,26,24,40,45,11,6,16,3,9,17,23,20,1,15,8,0,18,2,12,14,13,10,5,7,4,19,22,21}, + {45,29,44,25,33,39,27,37,26,40,46,38,43,35,34,30,41,32,24,36,31,47,42,28,23,9,7,4,6,14,2,21,20,17,11,8,13,3,16,1,19,12,15,0,18,5,10,22}, + {33,43,29,35,38,41,37,44,40,36,34,39,42,27,31,28,25,47,24,30,26,45,32,46,6,23,1,14,18,20,15,12,10,4,19,17,13,7,21,16,11,3,9,8,5,22,2,0}, + {28,30,37,41,43,39,34,24,38,46,31,36,40,45,29,32,27,44,47,35,42,26,33,25,17,23,5,11,18,20,4,7,19,15,3,16,1,8,21,14,10,6,13,2,22,12,9,0}, + {31,37,34,42,26,28,24,41,44,36,46,30,33,43,40,32,47,45,39,38,29,25,35,27,19,22,17,14,18,7,11,1,15,12,6,16,9,21,2,13,8,23,20,4,3,5,10,0}, + {45,43,26,37,41,40,29,36,32,39,47,38,24,34,46,33,27,25,28,44,42,35,30,31,2,18,22,12,1,0,9,20,14,4,7,10,16,19,15,5,3,6,8,23,11,13,17,21}, + {47,29,40,27,30,37,35,32,24,34,38,26,44,25,41,45,39,28,36,31,43,42,33,46,23,17,21,10,22,0,9,14,2,13,12,5,16,7,4,3,15,18,1,8,11,20,19,6}, + {36,44,29,45,24,39,47,35,32,46,33,42,34,40,31,26,41,27,38,43,37,25,30,28,22,1,9,23,6,3,2,15,12,21,19,0,17,20,18,14,4,11,13,7,5,8,10,16}, + {36,26,47,35,25,45,41,37,46,27,24,32,40,43,38,30,44,34,29,31,28,33,39,42,13,16,18,23,9,4,20,14,21,12,19,1,6,5,11,2,15,8,17,7,22,0,10,3}, + {27,43,42,24,31,38,25,47,32,28,30,39,29,37,35,41,40,45,44,26,33,46,36,34,23,12,4,6,16,19,17,8,18,9,13,2,1,3,15,11,21,14,20,7,5,10,0,22}, + {31,36,44,28,40,34,41,25,30,38,35,37,42,46,33,47,27,32,39,45,43,29,26,24,13,11,19,4,18,5,12,3,15,14,21,8,20,1,6,2,17,22,10,9,23,16,7,0}, + {28,27,42,26,47,43,34,31,33,40,38,25,24,44,32,41,45,29,46,35,37,36,39,30,9,15,3,19,4,10,12,16,2,0,20,5,21,6,14,18,8,11,22,1,13,7,17,23}, + {28,27,32,37,34,42,46,36,29,44,47,26,39,45,31,41,33,30,40,35,38,25,43,24,23,22,0,4,14,12,5,8,7,20,6,13,2,15,3,10,18,9,21,17,19,16,1,11}, + {24,35,31,38,30,37,46,26,32,43,29,36,34,42,40,39,28,33,41,47,27,25,44,45,3,15,8,2,22,16,19,6,4,17,12,14,20,10,7,13,1,23,11,21,0,18,5,9}, + {38,43,47,24,31,39,41,34,29,44,35,30,28,40,37,27,33,45,26,25,46,36,42,32,2,22,10,15,12,14,13,17,9,6,19,16,21,5,3,11,8,23,7,1,18,4,20,0}, + {27,41,25,33,45,28,35,37,26,32,42,47,31,38,46,40,24,30,44,36,39,43,34,29,0,21,9,22,13,12,15,2,10,19,8,18,7,17,20,4,1,11,23,3,14,16,6,5}, + {25,45,28,38,32,34,39,44,35,43,27,33,40,31,42,47,41,24,26,36,29,46,30,37,16,18,23,10,15,6,22,1,4,3,14,13,19,17,11,20,12,21,5,9,0,8,2,7}, + {37,42,27,41,34,36,32,35,46,31,30,47,45,28,25,40,26,39,29,33,43,38,44,24,21,22,3,8,14,1,15,23,6,11,10,13,18,2,17,12,16,9,0,4,19,7,20,5}, + {28,26,37,29,35,40,38,47,33,43,31,41,32,39,34,25,27,30,36,44,42,46,24,45,4,19,22,0,21,15,23,18,10,7,5,13,12,16,1,20,8,11,14,9,17,6,3,2}, + {27,30,28,34,31,26,42,29,33,37,45,38,43,25,24,47,32,41,44,36,46,40,35,39,12,15,1,21,11,5,17,23,10,14,2,4,7,3,9,8,19,6,16,20,18,0,13,22}, + {40,29,37,41,46,43,28,32,36,26,31,30,27,42,44,34,39,47,25,45,38,33,24,35,18,12,9,22,21,13,11,17,0,15,8,2,6,5,20,4,1,3,7,16,10,19,23,14}, + {24,45,30,40,26,32,29,37,47,27,46,44,41,25,33,36,39,28,34,31,43,42,35,38,11,22,18,21,17,20,10,2,14,5,9,19,15,6,13,7,12,4,0,16,1,3,23,8}, + {47,25,38,30,28,31,41,45,34,32,42,40,24,29,43,26,44,37,46,27,35,39,33,36,21,3,8,6,11,19,13,15,10,20,7,9,17,1,16,18,2,0,23,4,12,5,14,22}, + {30,37,36,39,26,40,32,29,35,47,43,24,38,45,42,46,28,34,44,31,41,25,27,33,6,1,12,2,20,16,19,13,11,8,17,23,15,21,22,3,5,10,14,7,18,9,0,4}, + {26,45,25,29,37,33,47,40,35,30,42,41,32,43,34,28,36,46,39,27,44,24,31,38,10,17,5,20,18,13,1,22,14,9,11,7,3,12,15,8,23,19,21,2,4,0,16,6}, + {37,29,36,25,28,33,31,34,43,46,26,47,42,35,40,24,45,27,39,32,38,30,44,41,4,11,7,1,22,6,3,18,23,16,10,9,2,21,13,17,12,5,15,19,14,8,0,20}, + {25,45,26,44,38,40,39,41,43,30,42,31,33,35,37,32,27,28,46,29,36,34,47,24,2,0,6,1,9,7,15,11,5,22,21,16,20,12,10,8,17,23,19,4,3,18,14,13}, + {25,38,42,26,44,34,47,33,46,30,40,45,36,32,27,31,41,28,39,37,43,35,29,24,22,4,8,16,15,9,6,0,18,14,10,1,17,3,5,2,21,20,11,7,13,19,12,23}, + {40,38,32,47,44,34,46,42,41,29,39,30,33,43,37,24,35,45,26,28,31,36,27,25,21,3,14,16,13,8,10,5,4,20,1,18,12,11,9,15,6,2,23,17,0,19,7,22}, + {34,32,47,35,24,39,31,33,41,38,30,43,26,28,36,44,42,40,29,27,46,37,25,45,10,23,0,14,22,1,19,9,20,7,2,11,4,16,5,21,6,8,17,3,15,13,12,18}, + {33,47,27,25,45,40,46,24,31,34,32,26,41,37,30,35,28,39,36,44,38,43,29,42,18,8,5,11,20,1,9,21,19,12,3,15,4,16,10,22,17,13,0,2,14,7,6,23}, + {46,27,29,24,35,25,45,47,31,40,30,28,39,42,33,44,36,41,32,26,34,37,43,38,15,17,4,1,22,7,10,9,8,14,20,19,13,18,3,21,12,5,0,11,23,6,16,2}, + {38,26,25,42,24,46,41,31,40,35,29,27,36,47,44,37,43,28,39,33,30,34,45,32,22,10,0,2,11,4,17,20,8,14,13,23,6,15,18,7,21,3,9,16,12,5,19,1}, + {46,33,29,43,34,41,31,27,39,24,40,45,35,38,28,37,42,26,32,44,36,30,47,25,6,10,22,8,11,19,3,16,5,18,21,2,14,13,20,7,0,12,15,23,9,4,17,1}, + {38,36,34,40,45,43,37,41,31,47,30,39,42,26,35,29,27,33,28,46,32,25,24,44,23,16,11,2,21,15,7,1,8,17,9,4,13,22,0,20,10,18,3,12,6,19,14,5}, + {46,33,31,26,42,24,43,39,47,29,32,35,44,41,36,40,38,45,37,28,27,34,30,25,2,16,14,1,10,15,4,6,17,11,13,7,9,19,3,0,22,21,8,23,20,18,12,5}, + {43,25,28,39,24,26,35,47,42,32,44,27,41,40,38,36,31,34,33,37,46,30,45,29,10,6,2,21,12,9,20,3,14,0,4,8,17,15,18,13,22,16,19,7,1,11,23,5}, + {43,37,31,25,30,40,46,44,39,32,24,36,29,47,42,35,34,26,38,33,41,27,45,28,12,3,5,0,4,1,23,18,13,16,19,6,8,22,2,11,17,9,14,7,21,10,15,20}, + {39,30,24,37,45,36,33,26,25,32,47,31,42,27,44,40,28,38,29,35,34,46,41,43,18,1,16,8,17,10,19,12,4,22,13,15,20,23,9,11,21,2,7,0,6,3,14,5}, + {38,30,41,43,26,28,33,27,36,42,34,31,29,45,40,47,25,44,37,46,32,24,35,39,23,17,8,14,10,15,5,3,9,7,4,19,16,2,0,18,21,20,12,1,11,13,6,22}, + {35,33,30,36,26,46,29,38,31,39,25,41,43,47,44,32,34,28,40,45,42,37,27,24,8,18,9,11,14,12,0,15,4,5,7,21,16,2,6,13,17,3,22,1,23,19,10,20}, + {26,34,45,42,27,33,38,25,35,29,41,47,24,32,36,31,37,30,44,40,46,39,28,43,19,15,21,16,9,11,6,20,8,14,2,17,3,7,1,22,12,10,4,18,5,23,13,0}, + {27,44,46,31,30,42,24,41,37,34,29,36,25,47,43,33,32,40,26,39,35,28,45,38,18,7,19,23,20,5,13,21,14,11,16,2,1,12,22,9,15,0,17,8,3,6,4,10}, + {44,27,45,40,31,28,26,42,36,39,41,47,35,38,34,46,29,32,24,43,30,25,37,33,14,2,23,19,18,17,11,4,0,8,6,21,5,22,1,9,3,20,13,16,12,7,15,10}, + {26,38,37,44,47,34,45,29,36,32,42,40,25,43,35,41,28,30,27,46,39,31,24,33,6,4,12,10,16,8,3,22,1,7,5,23,0,21,15,20,14,9,17,13,11,18,2,19}, + {41,32,31,39,25,29,35,43,34,45,42,47,46,28,37,30,26,40,38,36,44,27,33,24,13,18,9,11,5,20,4,0,21,17,10,14,2,23,12,8,19,15,3,6,16,22,7,1}, + {24,44,41,25,29,31,39,46,27,47,43,28,37,45,32,35,33,42,38,26,36,30,40,34,23,16,19,1,13,0,11,4,3,8,20,7,12,6,18,10,2,21,15,9,5,17,22,14}, + {44,31,41,26,39,28,32,24,36,40,46,25,43,34,33,42,47,45,37,29,38,35,27,30,20,1,9,22,5,15,7,10,16,0,2,8,19,21,14,6,18,17,23,3,13,12,4,11}, + {35,31,38,42,34,40,28,44,33,25,30,46,29,47,37,26,43,39,27,24,36,41,32,45,9,6,16,10,15,0,8,13,12,19,2,22,14,3,5,23,21,1,18,20,7,4,17,11}, + {39,44,47,46,36,33,26,41,37,24,42,32,45,38,29,28,30,40,34,27,35,31,43,25,22,16,15,17,21,11,13,5,8,4,20,10,23,3,2,0,9,1,6,19,18,14,12,7}, + {46,40,32,38,43,37,41,30,34,31,27,47,42,29,28,25,44,39,33,35,24,36,45,26,1,16,10,13,12,4,19,3,8,14,18,20,11,6,23,0,5,22,17,15,2,9,21,7}, + {30,42,46,38,28,32,27,26,36,29,44,31,39,41,45,33,37,35,34,43,25,47,24,40,11,3,7,16,19,13,20,15,21,2,12,0,8,6,9,14,23,5,1,4,10,18,22,17}, + {32,25,34,33,41,27,44,38,26,39,35,24,43,42,37,47,31,36,46,30,29,40,28,45,20,11,3,13,21,8,19,7,2,12,14,6,9,22,17,5,10,4,1,15,18,23,0,16}, + {43,35,31,47,26,42,27,44,46,34,36,29,28,25,37,40,33,41,32,24,30,45,39,38,1,3,12,0,19,10,6,17,11,8,22,23,15,5,21,2,14,4,13,9,7,18,16,20}, + {44,24,42,38,34,31,41,32,28,47,39,33,26,43,45,27,35,37,29,40,25,36,46,30,1,0,20,13,22,16,6,4,19,15,18,14,11,23,21,17,10,9,5,3,8,12,2,7}, + {32,42,24,40,45,27,26,47,29,46,34,39,30,28,31,41,44,43,36,33,25,37,35,38,13,14,17,9,21,19,8,22,7,12,10,15,5,23,2,11,0,4,18,6,3,1,16,20}, + {47,32,30,26,36,43,41,35,31,33,45,34,28,46,39,42,44,25,37,27,24,40,38,29,16,14,19,7,18,10,13,5,22,17,3,0,12,21,11,9,1,4,8,6,2,20,23,15}, + {36,32,44,25,47,38,45,30,35,28,26,31,40,46,42,37,33,43,39,29,24,34,41,27,22,13,16,4,20,8,19,3,2,9,1,23,12,17,0,6,15,21,10,5,11,18,14,7}, + {45,32,46,44,27,30,25,43,24,42,34,47,28,35,41,39,33,37,26,38,29,31,40,36,0,21,22,6,15,12,16,2,10,5,14,18,20,17,8,23,19,11,1,7,3,13,4,9}, + {39,28,34,30,36,43,38,32,25,41,45,44,29,35,31,47,26,46,40,33,27,37,42,24,23,19,8,11,7,6,13,4,12,14,20,3,21,10,16,2,1,17,0,15,9,22,18,5}, + {43,36,32,34,25,42,26,38,35,45,41,40,46,31,27,39,47,37,28,44,29,33,24,30,17,4,23,15,13,10,9,18,22,19,3,5,7,1,20,16,8,2,21,6,14,0,12,11}, + {41,46,39,37,42,30,29,38,28,32,43,45,36,31,34,25,33,26,40,44,27,35,24,47,13,21,18,4,0,11,19,3,7,2,9,16,20,1,22,15,14,23,12,10,5,8,17,6}, + {41,32,40,42,45,34,36,31,25,44,46,38,26,30,33,43,29,28,47,27,37,39,35,24,9,5,20,18,0,12,3,11,22,13,19,7,16,6,10,1,4,17,8,14,23,21,15,2}, + {35,25,28,43,33,46,37,34,29,36,38,27,44,32,41,47,31,45,39,42,26,40,24,30,23,4,3,13,20,19,18,21,1,8,10,22,15,14,7,6,17,2,16,5,0,9,12,11}, + {40,38,30,34,46,35,26,28,27,25,39,42,45,31,29,37,33,44,41,32,36,43,47,24,20,11,13,9,8,4,2,21,19,22,6,0,14,1,10,23,3,16,5,7,18,15,17,12}, + {44,38,30,34,42,29,27,46,39,41,25,35,45,37,28,43,26,32,47,36,40,33,31,24,22,4,16,11,20,19,18,2,21,10,6,9,8,14,0,15,13,3,17,12,5,23,7,1}, + {44,37,40,32,28,31,29,47,35,33,26,39,43,42,45,34,24,30,27,41,36,25,38,46,23,14,10,16,12,6,18,2,15,9,20,1,22,8,17,0,5,4,13,19,21,11,3,7}, + {41,29,32,38,24,45,36,42,25,40,27,31,30,35,37,34,47,26,33,44,46,28,43,39,22,18,14,3,13,7,23,4,1,17,0,12,9,20,8,11,15,6,5,16,21,19,10,2}, + {38,25,46,41,26,29,42,32,35,30,28,31,37,39,44,47,43,36,27,40,24,34,45,33,5,21,20,14,0,9,11,23,12,22,16,1,10,2,7,15,6,8,18,3,19,4,17,13}, + {35,30,43,25,44,39,27,24,32,29,36,31,38,45,26,28,37,34,40,33,47,42,46,41,11,7,15,22,3,6,13,17,16,4,20,18,23,5,0,9,2,21,10,14,8,1,19,12}, + {31,36,35,27,46,39,26,34,24,28,37,44,32,40,30,25,45,29,33,43,42,41,47,38,5,15,4,17,9,18,16,7,10,23,1,21,11,3,8,6,22,2,13,20,0,14,19,12}, + {45,41,29,42,47,34,24,31,36,43,25,28,32,44,27,38,35,46,40,26,39,30,33,37,23,18,15,1,6,12,14,0,4,22,11,8,20,17,19,10,5,21,3,9,7,13,16,2}, + {40,33,43,29,31,24,27,44,46,38,30,37,34,39,35,26,25,28,47,32,42,41,45,36,21,2,0,8,1,13,23,4,18,10,9,15,3,19,6,5,11,22,20,12,7,16,14,17}, + {26,37,31,45,38,41,29,33,28,42,30,36,32,34,46,27,44,43,47,40,35,25,24,39,22,17,10,21,8,5,13,20,18,23,15,2,19,7,6,9,4,11,3,0,16,14,1,12}, + {38,43,41,32,40,44,30,24,39,27,36,47,28,26,35,46,34,31,25,37,33,42,29,45,20,11,10,6,3,15,4,12,1,22,9,17,23,16,0,2,7,5,19,8,21,14,18,13}, + {47,31,45,37,41,27,43,36,29,40,35,44,26,32,28,24,46,39,42,25,33,38,30,34,0,21,2,6,23,13,10,20,19,9,11,22,4,7,5,8,16,14,1,15,17,3,18,12}, + {27,30,37,41,26,29,35,39,36,43,31,25,42,47,33,40,34,28,46,38,32,45,24,44,5,15,2,7,14,23,1,19,11,4,18,6,0,22,10,9,17,8,3,16,13,20,12,21}, + {38,34,33,30,25,43,42,36,31,37,24,35,32,41,39,46,44,47,28,26,45,29,27,40,1,5,3,2,12,18,16,4,20,10,17,9,8,13,19,21,11,0,7,23,22,14,6,15}, + {41,44,36,25,40,45,26,33,29,32,35,31,27,39,42,37,30,43,47,38,34,28,24,46,14,17,20,0,7,12,18,10,1,6,3,11,15,23,4,9,13,22,21,19,5,8,16,2}, + {33,40,39,37,46,30,36,28,27,25,41,38,42,47,24,44,32,31,43,34,26,45,35,29,1,19,5,7,16,11,13,10,21,20,4,22,15,18,14,6,9,3,8,17,2,12,23,0}, + {32,40,39,34,33,25,47,27,43,37,42,44,31,46,45,29,36,38,24,30,28,35,41,26,21,23,13,1,6,11,14,5,7,15,10,9,12,4,19,8,17,2,0,22,20,16,18,3}, + {24,32,36,44,40,30,34,28,45,31,41,37,39,25,43,33,27,47,42,46,29,35,38,26,12,7,18,14,6,2,23,22,20,1,4,13,15,11,17,19,10,3,8,16,21,5,9,0}, + {31,40,37,44,25,41,27,46,32,36,47,43,34,30,38,35,26,29,45,39,42,33,28,24,4,23,12,20,15,2,10,22,11,17,8,13,0,6,3,1,9,19,18,14,16,21,5,7}, + {38,40,36,47,26,37,46,34,24,27,31,43,35,29,39,45,33,42,28,44,41,30,25,32,5,16,11,6,12,2,4,15,1,18,9,0,21,20,23,3,22,17,13,7,14,8,10,19}, + {30,45,33,32,31,27,41,37,28,43,34,26,29,44,35,39,47,38,46,42,24,25,40,36,20,11,14,6,0,17,21,4,13,19,1,12,23,7,3,10,2,5,16,9,15,18,22,8}, + {24,38,31,29,37,45,47,41,46,26,32,42,39,28,35,30,44,25,36,43,33,40,27,34,23,20,9,8,19,3,6,21,16,15,22,1,10,4,7,18,5,12,2,0,14,17,13,11}, + {36,41,38,29,35,46,34,27,45,30,24,28,37,31,42,47,44,43,26,25,39,32,40,33,10,16,2,7,0,13,4,6,21,15,1,23,17,11,18,20,22,19,14,12,3,8,5,9}, + {33,46,28,27,24,31,30,36,38,40,26,32,37,25,41,47,42,35,45,29,43,34,39,44,22,16,5,14,19,3,9,21,11,8,20,6,4,12,0,13,15,18,10,7,23,2,1,17}, + {36,44,47,26,28,25,45,39,32,29,37,42,34,41,43,38,27,33,31,30,40,35,46,24,14,11,21,2,12,8,17,10,4,20,5,16,0,3,6,15,19,18,7,23,13,1,9,22}, + } + }, + { 49, { + {48,37,42,34,24,26,35,47,36,40,39,27,44,30,28,32,41,33,29,46,43,38,45,25,31,13,4,14,11,9,16,10,7,0,22,8,18,21,5,15,2,1,17,6,19,23,12,3,20}, + {47,45,38,32,34,41,26,40,36,33,31,30,29,43,35,25,28,39,44,46,48,42,37,24,27,11,9,23,18,0,2,5,21,14,17,1,22,20,13,8,16,12,6,15,4,19,7,10,3}, + {32,39,48,46,34,47,43,37,35,38,24,40,36,29,41,45,30,26,31,44,27,42,25,33,28,17,6,20,9,14,7,1,11,15,19,22,13,2,23,21,10,4,8,5,12,0,3,18,16}, + {43,34,36,31,25,47,35,39,29,28,26,40,45,46,30,24,38,33,27,41,48,42,44,32,37,4,15,18,20,8,6,22,17,23,1,14,2,13,16,7,9,19,21,0,3,12,11,5,10}, + {40,48,43,35,38,47,46,44,32,28,36,29,27,39,42,37,34,41,45,33,26,31,30,25,24,14,20,1,9,17,5,10,8,19,16,3,23,15,4,13,0,7,21,6,18,12,2,22,11}, + {33,26,36,35,47,44,30,48,29,27,34,46,38,32,40,45,39,28,43,42,25,37,41,31,19,17,2,22,13,20,24,10,7,18,5,16,23,1,21,15,0,11,3,9,6,8,4,12,14}, + {30,48,31,43,26,29,27,46,25,28,41,38,37,36,39,32,42,47,34,40,45,44,35,33,2,22,20,12,19,7,23,14,9,8,3,11,13,21,6,24,17,4,15,18,0,10,5,16,1}, + {25,36,40,44,39,29,37,42,41,28,31,35,45,26,27,30,46,34,47,43,33,32,38,48,17,0,5,3,19,1,15,21,18,20,2,16,6,23,14,10,7,12,9,24,22,8,4,13,11}, + {40,33,36,45,47,25,32,29,48,46,26,38,31,42,24,39,35,37,30,41,34,27,44,43,28,2,22,17,8,1,4,9,21,20,10,6,13,23,18,5,7,0,15,3,16,19,11,14,12}, + {28,38,30,37,40,48,39,42,41,45,34,33,35,26,43,29,47,46,32,44,24,27,36,31,25,5,16,4,19,13,0,9,12,1,7,21,10,20,15,14,3,17,6,11,8,2,22,18,23}, + {35,40,25,46,31,47,44,30,42,26,36,39,41,32,38,27,33,37,45,28,48,34,29,43,23,15,4,11,6,13,16,1,3,5,17,22,14,21,19,9,20,0,10,24,2,18,12,8,7}, + {28,43,45,44,46,48,27,42,33,41,39,25,30,29,32,36,47,26,38,31,35,34,40,37,11,23,22,21,16,18,8,24,20,19,9,14,0,3,5,17,13,6,1,4,12,7,2,15,10}, + {31,43,42,24,29,25,44,26,40,38,27,45,48,30,28,37,36,33,47,39,46,35,41,34,32,21,19,2,1,8,23,18,20,0,22,16,14,12,4,6,13,5,3,17,15,11,10,9,7}, + {36,46,44,27,26,35,32,41,37,48,45,24,40,28,38,47,29,42,34,39,33,43,31,25,30,4,15,5,9,3,23,12,17,21,8,1,11,14,22,2,13,10,18,20,0,7,16,19,6}, + {43,47,37,30,46,28,38,31,33,32,48,44,35,40,26,41,29,27,39,34,25,42,45,36,3,14,22,5,9,20,4,10,24,15,8,21,19,18,6,17,12,2,13,16,11,1,23,0,7}, + {42,30,44,47,37,39,41,35,33,40,45,34,26,29,32,43,38,28,36,48,46,27,25,31,1,13,3,19,4,8,5,7,17,11,9,2,12,21,16,6,20,10,0,15,23,22,18,24,14}, + {32,28,45,43,36,46,29,31,48,41,44,27,34,47,33,30,37,35,24,26,40,39,42,25,38,14,6,3,18,0,16,19,15,12,4,13,2,22,20,8,11,7,9,5,10,21,1,23,17}, + {44,26,32,30,42,34,28,39,43,41,29,47,33,38,46,40,48,45,25,37,35,27,31,36,1,15,17,6,4,3,23,8,10,18,16,14,2,19,0,22,20,24,12,7,5,21,9,11,13}, + {31,33,39,47,34,27,29,42,30,43,48,45,26,38,25,35,40,44,36,41,28,46,37,32,0,3,21,19,1,15,11,16,4,17,23,8,5,14,13,10,24,9,22,18,20,7,6,12,2}, + {26,36,39,33,48,28,40,42,29,47,41,45,37,30,34,25,43,46,32,31,44,38,35,27,10,4,0,2,16,11,9,1,7,24,14,6,19,12,21,23,22,3,18,5,20,8,17,13,15}, + {35,37,25,38,28,43,40,47,39,30,27,32,41,33,26,48,31,42,29,44,46,36,45,34,9,24,23,15,11,17,22,7,3,5,16,6,2,19,12,1,0,4,8,10,21,14,13,18,20}, + {40,36,43,25,46,41,37,44,26,34,30,45,39,27,38,42,24,28,47,31,35,32,48,29,33,2,11,7,17,12,18,3,21,14,6,8,1,9,5,10,13,16,15,22,23,20,4,0,19}, + {47,31,48,32,37,35,38,44,27,30,33,43,25,24,39,41,46,29,34,45,26,40,36,28,42,21,15,8,3,11,2,9,22,1,16,20,6,5,18,12,0,17,19,13,4,7,23,10,14}, + {48,31,38,32,46,28,42,36,24,43,39,45,34,33,26,29,35,41,27,44,40,37,25,30,47,15,9,17,23,11,0,21,14,7,5,3,2,10,22,8,6,4,20,16,13,19,1,12,18}, + {36,38,34,40,42,30,41,31,33,35,24,29,47,28,32,45,44,26,43,46,27,39,37,48,25,16,22,13,23,9,20,4,7,21,14,1,10,12,5,11,6,18,8,19,3,17,0,15,2}, + {29,40,44,46,33,37,42,35,39,41,27,47,32,25,30,48,26,43,45,31,36,28,38,34,12,1,10,5,3,9,24,15,2,4,8,11,16,14,0,19,6,21,13,18,20,23,17,22,7}, + {25,38,34,41,47,26,43,29,27,45,32,39,42,30,40,46,48,33,31,28,37,36,44,35,4,24,20,1,14,18,5,9,0,11,13,23,21,16,15,6,17,8,7,12,3,22,10,19,2}, + {35,38,42,32,30,41,33,28,36,31,45,48,40,44,37,46,27,39,34,26,47,25,43,29,8,17,1,5,10,2,14,4,12,0,15,21,3,20,24,6,19,18,11,7,13,23,16,22,9}, + {33,47,41,46,43,48,39,27,26,45,32,30,38,44,25,28,34,42,37,35,40,31,36,24,29,1,14,9,23,21,6,8,7,18,5,10,13,16,15,0,11,17,20,19,2,4,22,3,12}, + {26,43,46,37,35,34,32,39,48,29,45,31,33,28,47,44,40,25,36,41,30,42,38,24,27,6,19,5,10,1,20,18,16,12,0,21,4,9,15,14,2,11,8,23,13,3,7,22,17}, + {36,38,31,27,45,47,41,48,37,29,25,44,28,46,40,43,32,35,42,30,33,39,34,26,22,1,9,12,16,14,3,2,10,4,8,20,0,11,13,21,19,7,18,15,23,6,5,17,24}, + {37,48,44,27,46,43,29,28,38,31,45,36,24,35,41,30,34,32,26,33,47,42,39,25,40,2,9,12,18,11,14,22,7,16,10,4,8,6,3,19,23,21,15,0,5,20,17,13,1}, + {41,45,25,30,36,28,27,29,35,43,38,34,33,37,47,32,48,46,44,42,40,26,39,24,31,5,23,13,19,8,18,0,20,4,3,22,10,7,21,16,9,11,14,1,6,17,12,2,15}, + {26,33,31,30,34,25,41,38,45,39,42,29,27,37,48,46,43,36,44,47,32,40,28,35,11,21,17,14,13,6,22,2,1,18,20,19,0,16,3,5,9,24,10,8,12,4,7,15,23}, + {48,25,24,47,36,40,39,31,44,30,33,32,28,35,41,43,46,29,38,27,37,34,26,42,45,6,14,0,5,8,7,15,17,13,21,11,1,20,3,16,18,4,9,23,2,19,22,12,10}, + {27,37,31,45,26,44,48,29,36,47,32,28,42,33,43,35,41,30,25,40,46,24,38,34,39,11,0,23,8,19,6,10,4,21,2,20,22,13,15,1,17,3,18,12,14,9,5,16,7}, + {41,40,35,45,32,36,46,43,47,39,37,25,26,38,33,27,29,24,28,31,42,34,44,30,48,5,10,6,17,20,19,18,23,3,13,16,22,14,21,9,1,0,12,7,11,2,15,4,8}, + {26,34,28,48,43,45,33,27,42,36,39,41,35,44,38,40,30,25,47,37,32,31,46,29,14,19,2,21,22,9,13,18,3,8,16,15,23,17,5,7,12,11,6,4,1,24,0,10,20}, + {39,25,34,30,24,40,47,35,36,41,31,43,48,27,33,28,46,26,45,32,44,42,29,38,37,1,17,9,21,23,2,0,12,19,4,3,8,20,18,10,13,11,15,22,6,16,5,14,7}, + {33,45,40,31,47,37,34,44,30,29,24,26,46,39,28,32,25,41,35,43,38,48,36,42,27,13,23,17,12,19,14,3,1,15,21,6,2,22,8,10,5,4,11,0,16,7,18,20,9}, + {32,26,36,35,37,29,39,42,34,47,43,31,24,48,28,33,41,30,46,44,40,27,38,25,45,4,10,0,22,5,8,19,13,2,16,20,7,14,21,9,15,12,23,6,18,3,17,11,1}, + {36,35,26,32,27,31,33,45,28,46,38,42,48,34,40,47,30,41,44,25,43,39,37,29,5,20,17,7,16,4,2,1,8,3,23,10,18,15,21,13,9,11,6,19,14,22,24,0,12}, + {35,42,48,40,30,38,31,27,29,25,47,43,28,39,37,36,44,33,46,45,41,34,32,26,0,12,3,17,15,9,23,1,5,18,6,16,21,7,11,22,2,8,14,4,20,13,10,24,19}, + {48,30,35,28,40,44,34,41,26,36,27,33,39,46,42,24,43,32,37,47,31,25,38,45,29,16,9,3,18,2,15,0,8,17,19,5,22,12,14,23,6,4,10,13,21,11,7,1,20}, + {24,31,47,32,34,45,42,26,33,40,37,35,25,30,38,46,28,39,43,36,41,48,27,29,44,11,14,23,8,10,22,3,0,4,2,9,12,15,18,13,6,16,19,7,21,20,5,1,17}, + {38,44,34,46,37,36,43,41,32,31,47,40,25,42,30,39,24,29,35,28,27,45,48,26,33,6,16,11,18,14,3,0,2,15,22,9,13,17,12,4,20,5,21,7,1,23,19,10,8}, + {48,34,28,36,46,44,40,25,42,41,27,38,29,26,47,35,24,39,45,43,32,31,37,33,30,21,13,19,9,12,23,14,5,8,7,15,17,2,10,4,6,16,3,22,20,11,1,18,0}, + {32,29,47,43,27,30,24,40,33,44,35,45,39,42,26,28,36,38,37,41,48,34,46,25,31,22,0,17,6,14,10,20,18,7,21,9,4,2,19,13,15,1,23,5,11,8,16,3,12}, + {28,47,38,26,39,36,27,32,29,40,33,30,44,31,25,48,42,34,46,37,41,43,35,45,22,14,9,18,8,21,11,2,17,24,19,13,5,20,10,4,23,7,16,15,12,0,6,1,3}, + {43,44,41,45,38,46,29,48,39,25,28,31,36,47,32,37,33,26,30,40,35,27,42,34,9,6,3,24,10,1,8,11,14,16,23,20,4,17,2,21,19,13,12,0,18,15,5,22,7}, + {32,40,26,33,37,39,28,35,44,46,34,38,47,25,48,45,41,27,30,36,31,43,42,29,22,9,23,11,5,7,3,0,8,24,18,20,15,14,12,1,16,19,10,6,21,17,2,4,13}, + {39,43,33,28,35,44,26,27,24,30,32,42,38,45,48,34,31,29,41,37,36,25,47,40,46,5,23,7,9,17,21,16,0,2,15,14,20,4,12,22,6,18,11,1,3,13,19,10,8}, + {30,29,47,37,31,44,24,26,32,28,48,36,42,40,45,35,27,33,46,38,34,25,39,43,41,9,8,10,3,12,6,13,15,17,21,14,11,18,0,19,2,4,1,22,5,20,7,16,23}, + {40,27,25,43,33,42,38,46,31,29,44,37,36,39,45,48,34,26,41,32,47,35,30,28,2,1,17,12,16,22,18,8,19,13,23,6,4,11,15,5,0,3,20,9,7,14,21,24,10}, + {27,45,36,31,38,33,47,46,48,42,37,40,35,34,41,39,26,43,30,28,25,44,29,32,5,7,24,16,9,8,1,23,2,6,13,10,20,19,18,3,0,14,22,15,12,17,11,21,4}, + {42,31,47,33,27,40,29,41,45,26,25,37,44,43,46,30,28,32,38,36,35,39,34,48,22,3,24,1,5,2,13,6,9,14,0,12,7,18,20,11,19,8,21,23,15,17,4,16,10}, + {40,27,45,38,47,26,41,25,34,42,28,36,29,32,35,37,24,39,46,33,30,43,31,48,44,21,19,9,0,18,10,3,7,16,15,2,12,17,14,4,8,6,22,1,23,5,11,13,20}, + {42,28,31,40,45,35,29,46,32,37,41,38,44,34,26,30,33,43,36,47,25,27,39,24,48,23,21,14,9,0,5,20,6,18,4,12,7,15,17,19,8,11,13,1,3,10,2,16,22}, + {39,32,30,29,36,31,34,46,25,35,38,33,28,41,48,44,43,27,26,40,37,47,45,42,10,8,19,3,12,21,6,18,15,16,20,17,4,7,0,24,22,13,23,11,1,9,5,2,14}, + {27,40,37,35,25,26,28,48,45,29,33,31,43,41,32,39,42,47,46,44,34,30,38,36,7,24,19,21,16,8,14,3,10,22,17,9,1,13,11,6,0,23,4,12,15,5,2,18,20}, + {45,47,31,25,37,46,35,44,40,42,27,26,41,29,34,36,28,32,39,33,48,30,24,43,38,23,9,12,2,20,5,7,4,22,0,21,11,1,3,14,13,19,17,10,6,8,16,18,15}, + {25,38,48,44,37,45,35,39,26,32,29,47,36,40,31,30,33,27,42,46,28,41,43,34,7,9,13,24,5,12,17,0,2,8,6,19,1,16,10,18,20,4,11,3,23,22,15,14,21}, + {47,41,29,37,27,43,38,36,39,26,28,45,48,42,40,30,33,32,35,34,44,46,31,25,23,12,11,15,22,17,24,9,3,20,8,7,2,21,16,14,1,19,5,4,10,18,13,6,0}, + {39,46,28,30,47,40,27,32,45,43,41,44,37,25,38,31,26,35,42,48,34,33,36,29,13,10,18,17,20,12,8,6,0,22,3,7,24,15,5,14,16,2,4,1,21,19,9,11,23}, + {42,41,43,38,32,47,25,46,37,26,30,44,34,29,31,33,36,35,40,45,27,39,48,28,11,20,21,1,13,10,18,16,3,17,4,24,15,5,7,12,2,6,19,9,23,22,14,0,8}, + {46,42,48,27,35,44,37,45,39,29,33,36,28,34,40,43,26,41,30,32,38,47,31,25,23,16,3,21,20,14,6,1,10,17,24,11,4,19,7,18,12,9,15,5,0,8,22,2,13}, + {47,46,39,38,29,31,36,40,44,48,27,45,30,26,28,35,25,32,24,34,42,33,43,41,37,17,21,4,11,6,22,12,1,19,3,0,23,8,10,20,15,2,13,16,18,14,7,9,5}, + {25,43,47,31,30,46,44,34,36,48,27,35,42,33,39,28,32,29,45,41,40,38,26,37,10,1,21,14,7,6,11,15,22,12,24,2,8,23,5,3,0,19,13,18,9,4,17,20,16}, + {37,34,32,40,30,36,42,45,24,48,43,28,31,44,39,27,46,33,26,38,47,35,41,29,25,13,17,19,3,7,2,23,14,5,10,0,4,9,15,1,21,18,12,11,8,20,22,16,6}, + {35,48,41,24,36,33,31,34,32,45,37,30,40,25,43,26,38,47,44,28,27,29,39,46,42,20,6,0,11,23,3,17,1,15,21,10,4,16,22,18,9,12,19,14,8,7,5,13,2}, + {43,47,37,27,40,33,26,41,29,42,39,34,46,38,30,35,44,31,36,32,28,48,45,25,5,16,18,13,9,19,14,0,6,2,7,1,3,10,4,23,20,12,11,17,22,15,8,24,21}, + {31,40,43,47,29,26,30,28,37,36,41,25,42,46,44,35,27,34,38,32,48,45,33,39,17,6,5,3,18,8,2,14,19,11,4,0,15,24,7,20,13,10,16,22,12,9,23,21,1}, + {33,31,37,26,32,44,39,47,42,34,43,48,38,46,41,36,45,30,35,27,29,24,25,40,28,15,19,2,4,0,11,13,7,6,17,3,1,8,14,16,20,22,12,21,10,5,18,23,9}, + {38,31,30,35,46,39,32,36,48,33,40,24,29,28,27,26,47,41,37,34,42,44,25,43,45,21,4,17,15,1,19,7,12,2,20,6,11,10,3,8,23,22,9,16,18,0,14,5,13}, + {47,35,25,44,32,29,48,39,42,31,45,36,41,26,34,46,37,27,43,30,38,28,33,40,2,8,20,0,9,3,5,19,14,6,4,10,18,16,12,22,24,1,23,11,21,15,17,13,7}, + {45,35,33,47,43,31,41,27,44,26,30,28,40,46,29,25,39,42,48,36,32,37,38,34,1,12,8,23,19,9,16,24,17,2,15,5,21,14,11,0,7,6,22,20,18,10,4,3,13}, + {35,41,27,47,44,39,46,30,45,25,42,29,31,48,28,33,26,36,43,34,24,40,32,37,38,3,6,19,4,11,17,15,10,23,8,1,12,0,7,5,2,14,16,21,20,13,9,18,22}, + {37,42,24,41,44,48,31,46,34,27,33,29,32,39,28,40,35,38,30,36,25,43,26,45,47,8,10,14,17,21,5,22,19,6,20,18,16,2,13,4,15,9,7,1,0,3,12,23,11}, + {28,45,31,25,33,41,32,40,38,36,30,27,44,26,46,37,43,34,47,39,29,35,42,48,1,0,19,18,21,4,20,17,23,3,2,9,16,12,11,5,15,24,22,7,10,13,8,14,6}, + {30,25,48,40,33,35,41,29,46,39,37,47,36,38,43,28,44,32,31,42,45,26,34,27,12,19,8,23,11,20,18,21,17,4,2,5,24,10,13,9,3,1,6,14,16,15,0,7,22}, + {38,33,42,48,25,34,46,32,28,27,47,35,37,30,39,44,40,29,43,36,26,45,41,31,21,18,6,12,17,3,16,23,2,24,22,11,14,1,9,5,19,8,15,10,7,20,4,0,13}, + {40,33,47,44,30,43,26,38,41,39,31,35,37,27,32,48,29,42,34,45,28,36,46,25,3,13,15,5,9,0,14,22,16,2,18,11,7,20,24,23,21,12,6,1,19,8,4,10,17}, + {46,28,44,40,31,38,35,41,30,36,45,37,48,27,32,43,34,33,26,25,47,42,39,29,23,11,17,21,2,9,7,16,5,13,10,19,14,4,18,8,22,20,1,24,3,15,12,6,0}, + {26,32,45,25,30,42,31,37,34,46,38,47,27,33,29,43,36,24,48,41,39,35,28,44,40,20,15,19,2,1,13,5,14,16,23,0,7,4,11,18,9,12,8,21,17,3,22,10,6}, + {42,28,38,36,29,41,40,26,32,44,27,39,31,48,43,46,34,47,33,37,45,30,35,25,3,9,18,7,1,4,11,6,21,23,16,24,20,19,2,8,12,17,0,14,22,10,15,5,13}, + {38,26,41,33,37,46,28,24,44,42,45,40,36,43,48,30,25,34,47,39,31,29,35,32,27,9,21,6,16,15,22,0,23,10,13,12,5,3,19,2,7,14,4,18,20,1,11,17,8}, + {47,40,33,43,35,48,26,44,38,46,36,28,27,29,39,42,41,32,45,34,24,31,37,30,25,6,12,4,13,2,1,21,20,17,10,23,18,8,0,14,5,16,11,3,7,19,9,15,22}, + {32,38,47,36,45,41,44,26,31,48,34,29,27,46,39,37,43,30,40,28,33,35,25,42,15,23,11,13,9,6,5,16,20,2,7,21,10,19,1,14,12,3,22,4,0,17,24,18,8}, + {25,46,37,34,40,42,30,35,33,36,43,41,48,29,32,47,39,44,38,27,26,31,28,45,22,5,24,20,16,0,14,6,3,1,17,4,23,15,8,11,9,12,10,2,18,7,19,21,13}, + {36,47,41,44,30,27,39,43,35,34,25,38,31,40,28,29,42,37,32,26,46,48,33,45,14,24,17,1,7,2,4,6,22,16,21,20,0,12,23,11,10,15,9,13,18,8,19,5,3}, + {48,47,35,41,28,25,44,30,32,26,29,31,36,46,33,43,39,34,27,38,40,42,37,45,23,1,14,22,3,10,12,8,16,4,13,19,17,20,9,0,11,6,24,5,18,21,7,15,2}, + {31,42,35,34,47,30,46,45,38,33,43,48,25,41,27,40,29,37,32,28,39,26,36,44,5,12,10,6,19,3,24,21,18,1,16,2,9,13,8,0,15,17,14,23,4,7,22,20,11}, + {47,25,37,26,45,30,43,33,48,38,29,24,40,27,46,42,44,32,31,35,39,28,36,41,34,1,8,13,15,12,20,3,17,14,22,19,16,10,0,5,7,23,21,6,11,4,2,9,18}, + {44,48,43,35,45,39,37,32,30,24,36,41,46,29,31,40,38,26,33,47,25,42,34,28,27,3,15,0,17,23,4,14,11,21,12,19,18,1,16,9,5,22,13,10,20,2,7,6,8}, + {43,33,26,46,35,48,47,42,40,39,28,34,25,29,37,30,45,24,32,36,27,41,44,31,38,2,4,18,14,13,11,6,1,0,12,22,7,16,9,17,20,15,23,19,21,5,8,3,10}, + {30,45,33,35,43,41,26,25,28,27,42,48,34,36,29,47,44,38,46,40,31,37,39,32,7,22,0,19,21,5,17,2,13,16,8,4,11,1,12,9,15,24,20,14,18,10,6,23,3}, + {29,31,25,33,42,40,43,37,45,48,38,35,27,26,44,28,47,41,39,30,34,36,32,46,1,21,3,13,19,10,0,11,23,7,16,20,17,6,14,8,2,4,12,24,9,5,18,15,22}, + {24,29,44,30,41,47,39,35,45,36,42,34,37,46,32,31,40,25,43,28,33,48,27,26,38,17,19,6,8,1,20,5,18,9,15,2,23,16,13,7,21,11,10,22,14,3,0,12,4}, + {46,40,28,31,33,39,44,34,41,27,25,26,48,30,42,37,45,36,47,29,43,35,32,38,23,19,24,3,6,16,1,11,20,18,13,22,9,5,17,8,15,7,12,21,10,0,14,2,4}, + {35,45,36,39,29,44,28,48,46,40,25,41,32,30,26,34,38,31,43,33,47,37,27,42,23,21,24,20,3,0,12,8,7,10,17,14,19,22,13,6,9,11,1,5,18,4,16,2,15}, + } + }, + { 50, { + {33,45,47,40,34,39,46,48,28,25,38,37,26,42,30,36,43,41,49,27,35,29,31,44,32,6,22,17,1,11,2,18,16,20,9,21,14,13,19,23,4,15,7,5,0,24,12,10,3,8}, + {29,41,48,37,25,33,26,34,39,47,49,32,45,28,38,31,46,40,44,43,30,36,42,35,27,15,14,23,3,16,13,11,21,4,7,9,12,18,22,2,19,1,6,0,20,5,10,17,24,8}, + {36,45,42,44,30,34,29,38,32,27,26,40,35,39,33,46,43,48,37,28,47,49,41,31,25,13,5,11,2,19,18,23,7,20,22,16,0,4,17,9,1,21,15,6,24,3,8,12,14,10}, + {37,28,43,25,35,39,47,30,45,33,46,34,31,41,29,32,44,38,48,42,36,27,40,49,26,18,4,12,1,16,19,5,15,21,23,20,11,17,0,22,9,7,3,2,13,14,10,24,6,8}, + {48,35,30,27,36,25,37,32,44,29,45,47,41,39,26,28,40,31,33,38,42,34,46,43,49,2,9,15,22,6,12,17,10,18,21,16,19,13,0,24,7,23,3,11,8,5,4,1,14,20}, + {45,42,48,37,29,28,35,26,49,33,38,40,39,36,47,32,43,31,27,41,30,34,46,44,25,5,4,12,7,14,10,20,11,19,0,6,16,9,18,23,22,17,13,21,24,3,15,8,2,1}, + {45,31,26,27,40,38,29,47,33,32,46,44,36,35,34,41,48,43,39,49,28,37,30,42,25,1,16,18,14,6,9,13,5,24,20,8,15,10,21,11,23,7,3,17,4,0,22,12,2,19}, + {43,25,32,45,37,46,34,36,31,29,40,47,39,27,30,38,44,42,48,28,33,35,41,26,49,5,8,23,15,2,10,20,24,14,6,4,21,3,11,22,0,12,17,13,16,19,7,1,18,9}, + {47,29,48,30,40,45,26,39,46,34,33,49,31,42,32,44,38,28,35,27,43,37,41,36,25,24,4,11,16,19,17,9,8,15,6,18,23,21,1,14,3,10,20,2,0,13,5,7,22,12}, + {45,32,37,39,28,31,36,46,49,27,40,44,42,35,33,41,30,26,43,34,29,48,25,38,47,0,7,9,1,17,20,6,11,5,2,21,14,3,12,4,10,24,18,13,16,23,8,15,19,22}, + {31,26,35,39,47,46,37,41,49,40,48,29,45,27,36,32,28,43,25,34,33,42,30,38,44,17,15,13,9,12,23,7,21,6,2,11,24,3,10,14,8,5,22,4,0,16,1,18,20,19}, + {43,25,42,49,34,26,35,29,48,28,39,36,31,33,47,38,27,45,32,44,40,30,37,46,41,14,4,21,9,24,7,3,18,0,17,15,19,10,5,11,1,12,6,2,8,20,13,23,16,22}, + {34,42,29,33,25,48,37,30,41,32,31,43,39,36,45,35,38,46,26,49,40,44,47,28,27,20,18,2,8,17,14,16,9,24,22,15,12,6,0,13,1,19,4,11,7,21,10,3,5,23}, + {26,43,49,30,38,37,27,34,46,35,29,41,28,33,42,45,31,25,39,44,40,36,48,47,32,8,22,20,13,15,6,3,1,24,18,5,11,14,9,21,23,10,19,4,12,17,7,16,2,0}, + {28,42,29,30,45,47,32,49,39,35,46,37,48,44,40,33,38,36,34,27,41,43,31,26,25,24,17,21,4,6,2,12,1,8,10,22,5,16,19,13,0,9,14,18,3,15,11,7,20,23}, + {46,39,38,27,29,49,42,34,28,37,26,40,36,25,48,44,33,30,32,45,31,47,35,43,41,23,8,13,6,19,10,0,17,1,22,21,15,14,16,4,18,20,7,3,5,9,2,24,12,11}, + {30,41,48,31,33,35,49,32,36,28,38,37,34,42,46,44,29,39,27,45,26,47,43,25,40,24,17,0,11,1,7,5,13,20,23,14,8,10,19,9,22,15,6,18,4,2,16,3,12,21}, + {41,30,38,49,48,26,36,32,34,46,43,28,39,45,44,42,47,29,37,35,33,31,27,40,25,18,6,22,20,0,21,4,23,16,10,3,24,5,9,11,1,14,8,15,17,12,7,2,13,19}, + {42,31,27,46,40,37,39,30,49,35,38,34,28,41,48,47,32,36,29,33,45,44,26,43,25,13,21,11,9,19,22,5,8,3,20,1,17,23,24,6,0,2,10,7,16,14,12,4,15,18}, + {28,31,26,49,37,48,47,42,45,29,36,32,35,33,41,34,46,30,40,44,39,25,27,38,43,15,4,24,14,1,9,6,5,10,13,2,21,19,12,20,17,8,7,11,18,16,22,23,0,3}, + {45,32,46,39,33,31,38,42,30,25,48,44,29,28,43,40,35,26,49,37,47,27,36,41,34,9,23,5,14,1,17,4,2,15,0,3,24,13,11,19,10,8,18,12,7,6,22,20,16,21}, + {35,40,39,28,43,38,27,26,25,42,45,29,33,36,32,34,30,41,47,31,37,49,48,44,46,24,8,23,11,2,13,3,12,7,4,16,19,6,18,17,22,5,1,21,9,14,20,10,15,0}, + {42,47,30,37,31,33,48,38,34,41,40,29,35,32,27,44,39,49,45,26,46,28,36,43,25,9,5,10,20,3,15,0,7,14,21,18,1,12,6,19,11,24,16,23,22,8,17,2,4,13}, + {34,46,31,37,41,39,28,45,36,40,35,29,32,43,47,26,49,27,44,33,48,42,38,25,30,20,22,14,21,16,15,1,8,5,10,9,18,17,2,11,13,6,23,4,19,3,7,0,24,12}, + {38,46,32,44,26,33,40,37,29,31,39,47,49,43,30,42,34,41,36,35,28,48,25,45,27,6,0,2,4,15,3,18,5,11,16,19,13,12,20,10,17,9,1,23,22,14,8,24,21,7}, + {35,25,47,28,39,44,40,37,46,26,48,41,33,49,43,34,42,36,32,31,29,45,38,30,27,21,11,5,4,17,6,14,18,22,19,13,16,3,12,2,7,10,8,24,23,20,9,0,15,1}, + {27,26,32,44,43,25,46,37,28,45,39,49,38,40,35,30,29,34,42,48,31,36,41,33,47,15,23,20,24,17,22,5,19,14,4,12,7,6,1,10,3,0,18,13,9,21,16,11,8,2}, + {29,34,41,37,33,30,43,42,25,46,36,26,28,39,32,48,38,35,49,31,40,45,44,27,47,17,6,22,3,9,7,2,11,20,15,5,13,16,23,10,18,0,8,12,19,1,14,4,24,21}, + {49,39,29,27,35,28,46,48,45,41,38,25,26,34,32,44,31,40,42,47,33,36,30,43,37,22,9,3,18,15,6,8,2,24,10,13,12,1,16,11,5,4,21,20,7,17,14,19,0,23}, + {45,37,32,42,28,30,46,36,39,44,35,41,26,38,33,31,49,40,48,47,29,34,43,27,25,24,12,11,3,16,13,5,21,19,17,1,9,22,10,18,2,7,0,23,4,14,20,8,15,6}, + {44,32,27,40,33,26,49,31,39,46,36,29,42,34,37,28,30,47,43,41,48,35,45,38,25,23,22,0,2,16,8,15,9,11,13,12,6,3,19,5,1,21,18,24,20,4,14,7,17,10}, + {40,44,29,49,25,33,43,35,30,26,48,42,41,32,28,27,38,36,31,46,37,45,34,39,47,24,0,22,21,23,11,14,10,19,5,15,20,17,16,18,6,12,4,7,9,1,8,3,2,13}, + {49,32,41,47,46,36,25,45,30,28,48,38,42,34,37,26,35,43,29,40,27,33,39,44,31,24,9,21,1,13,16,7,20,14,8,0,17,2,4,22,3,19,12,6,10,5,11,15,23,18}, + {43,41,25,37,34,48,27,29,26,31,49,40,38,30,46,33,28,47,36,32,45,42,39,44,35,23,22,2,18,17,11,13,7,0,14,12,15,1,8,5,19,6,16,10,20,9,4,24,3,21}, + {38,40,26,28,42,37,32,41,44,49,27,45,48,30,36,47,39,29,46,31,34,33,25,35,43,24,2,1,17,4,15,8,18,6,20,3,12,22,19,10,5,11,14,9,0,13,16,23,7,21}, + {30,32,42,38,47,46,28,26,34,25,39,44,48,35,33,43,36,27,49,29,40,45,37,31,41,10,4,8,19,12,14,1,0,24,5,3,16,22,18,11,20,21,2,9,23,15,6,13,17,7}, + {25,45,49,26,40,46,36,34,44,47,27,31,43,41,29,33,48,28,39,32,42,30,38,37,35,24,14,0,8,13,18,3,1,11,17,9,12,19,7,4,20,15,5,2,22,10,16,6,21,23}, + {39,25,41,29,32,40,26,36,27,45,34,33,37,47,30,49,28,38,43,35,44,31,42,46,48,24,18,8,12,20,11,7,0,21,15,19,9,3,17,10,13,2,16,1,5,23,14,6,22,4}, + {38,47,46,39,48,29,35,34,41,30,45,43,32,44,28,40,49,31,37,33,42,26,36,25,27,24,6,22,8,19,0,3,2,16,11,1,21,15,14,5,7,20,13,23,10,4,17,12,18,9}, + {42,41,28,30,33,44,25,45,39,47,40,46,31,36,34,27,49,38,37,29,43,48,35,32,26,16,2,19,0,7,5,11,8,14,17,15,1,22,6,20,9,4,24,10,21,12,18,3,13,23}, + {47,30,32,49,35,25,45,43,31,36,38,27,34,41,39,29,28,48,37,44,40,33,46,42,26,19,2,9,18,10,23,11,17,20,8,4,21,3,1,14,16,13,5,7,15,6,22,0,12,24}, + {28,42,44,47,34,38,41,32,27,40,25,35,39,26,30,29,43,49,45,31,48,36,33,46,37,20,22,24,23,9,6,8,11,2,17,16,15,0,13,18,5,10,3,14,4,12,21,19,7,1}, + {47,37,41,43,31,45,44,39,30,46,27,33,35,38,28,32,29,48,42,34,26,40,36,49,25,10,15,22,4,0,12,9,21,19,16,7,20,11,23,2,13,17,14,3,18,6,8,1,5,24}, + {44,41,27,49,37,29,40,33,25,46,26,28,36,42,48,43,45,32,34,31,38,35,47,39,30,24,10,7,17,23,13,15,2,9,19,6,11,14,22,20,16,3,12,5,0,21,18,1,4,8}, + {27,47,33,35,34,25,32,48,29,36,49,31,41,40,28,38,37,46,42,39,44,26,43,30,45,24,7,20,3,16,8,21,19,15,2,14,22,10,23,11,18,1,0,12,5,13,6,9,17,4}, + {25,34,41,29,45,37,33,42,44,47,43,31,49,26,30,48,35,46,39,28,27,36,38,32,40,22,1,23,0,16,5,9,13,15,19,8,10,2,7,6,12,20,17,4,11,18,3,14,21,24}, + {34,33,44,47,36,42,37,49,28,39,48,35,29,40,31,30,41,27,38,46,43,26,32,25,45,13,9,14,17,20,16,8,1,15,18,10,19,0,22,11,7,23,4,3,24,21,6,5,12,2}, + {49,31,39,47,27,30,42,46,33,44,38,29,26,45,37,48,32,41,34,43,36,40,35,28,25,24,7,16,2,23,11,6,13,10,19,15,12,8,4,17,0,3,14,18,22,20,5,9,1,21}, + {27,48,32,44,41,39,38,37,28,35,26,45,30,29,36,40,25,46,33,47,43,49,42,31,34,8,23,0,3,21,12,10,20,18,24,19,14,7,6,1,22,17,16,2,15,9,11,4,5,13}, + {40,28,42,38,32,47,30,39,27,36,25,46,37,29,45,34,31,33,43,26,44,35,49,48,41,22,21,19,16,1,6,23,4,7,3,13,2,11,8,12,14,18,0,17,10,20,24,9,15,5}, + {39,30,32,29,34,48,28,31,41,27,40,37,42,36,38,33,35,25,43,45,26,49,44,47,46,5,8,15,12,3,21,0,6,19,14,11,23,10,4,16,24,7,17,9,22,2,13,1,18,20}, + {33,38,30,27,35,43,34,49,32,47,39,31,40,42,25,46,28,44,37,48,26,41,36,45,29,18,13,9,7,4,10,12,5,16,3,14,23,6,11,21,0,24,17,22,19,8,2,1,15,20}, + {36,44,35,37,27,32,26,46,49,34,29,43,40,30,48,45,33,39,25,42,28,38,47,41,31,5,13,17,3,10,12,8,24,0,23,2,11,16,22,7,20,6,14,19,1,4,21,15,18,9}, + {40,39,25,29,42,49,48,31,28,38,46,36,26,43,33,32,41,45,34,44,27,37,35,30,47,11,13,22,2,19,12,9,15,18,20,10,6,8,24,1,0,3,4,23,17,16,14,21,7,5}, + {44,49,33,45,39,38,47,34,42,26,46,32,29,40,28,27,43,25,36,41,37,48,35,30,31,9,16,8,11,3,6,5,23,15,21,13,19,22,17,20,14,2,0,12,1,24,4,18,10,7}, + {32,45,35,31,33,36,41,46,26,30,40,28,49,43,29,39,44,37,47,34,27,42,25,48,38,24,16,5,4,1,7,14,6,9,0,13,22,10,20,15,3,2,18,8,11,19,17,21,12,23}, + {44,30,29,38,40,48,36,32,39,31,33,28,41,26,35,37,34,42,47,46,49,27,45,43,25,18,2,11,21,23,10,17,8,24,5,14,22,4,0,19,15,1,12,3,7,6,9,16,13,20}, + {44,36,45,33,42,34,46,40,47,25,32,38,35,27,48,26,30,29,41,49,43,37,31,39,28,14,22,13,11,17,2,18,12,3,4,15,20,6,8,23,1,9,19,7,0,21,16,24,10,5}, + {41,37,43,28,42,45,40,48,34,25,39,33,29,31,35,47,44,27,46,36,38,49,30,32,26,5,20,1,7,13,19,22,9,24,15,12,6,2,16,0,18,8,21,23,11,17,3,14,4,10}, + {45,36,38,48,44,46,40,37,26,32,35,34,30,41,25,27,49,31,33,47,39,43,29,42,28,11,22,20,8,16,4,13,3,1,21,2,0,18,12,15,19,9,5,23,6,14,17,24,7,10}, + {38,49,48,39,28,37,47,33,44,42,32,29,43,36,31,27,41,40,46,35,26,30,25,45,34,14,20,24,7,9,13,3,2,21,12,5,23,0,11,17,6,4,19,22,15,8,10,16,18,1}, + {30,46,32,36,29,35,28,39,41,27,31,26,49,40,44,42,37,43,25,45,34,38,33,48,47,22,9,23,0,15,6,8,17,13,20,19,18,12,16,3,24,1,5,7,2,4,11,10,21,14}, + {32,30,38,43,26,42,48,40,33,27,36,29,34,28,35,41,44,49,37,47,46,45,39,25,31,1,6,20,9,15,5,12,24,2,0,22,7,13,16,14,3,18,10,8,11,4,21,23,19,17}, + {27,45,25,41,46,36,43,49,47,44,29,32,40,39,48,35,31,38,37,33,42,26,34,30,28,22,17,16,0,21,1,10,15,20,12,11,24,7,23,14,19,5,4,3,9,18,6,8,13,2}, + {30,39,48,28,45,27,43,46,33,44,38,34,40,36,42,49,26,25,41,29,47,32,37,35,31,23,4,13,18,1,22,3,21,19,17,7,14,6,5,9,24,20,8,16,15,11,2,10,0,12}, + {47,40,35,39,37,30,34,41,31,26,42,44,43,38,28,36,29,45,49,46,32,27,33,48,25,8,11,15,19,16,24,18,0,12,17,2,4,20,22,6,3,23,7,10,9,5,21,14,1,13}, + {31,49,32,26,30,27,40,42,46,43,35,28,34,41,47,44,25,33,36,48,38,45,39,37,29,23,8,17,3,21,10,1,19,16,13,6,4,2,14,12,7,24,22,15,18,0,5,11,20,9}, + {30,48,38,33,45,49,32,43,36,41,37,44,47,39,35,34,29,42,46,31,28,26,40,27,25,20,10,2,11,23,16,1,18,24,6,19,15,8,12,17,14,4,9,13,5,3,21,0,22,7}, + {37,40,26,42,38,43,27,39,34,44,29,41,47,35,33,49,46,45,36,32,48,31,28,25,30,23,1,0,11,19,18,16,5,9,20,24,3,15,21,2,12,13,17,8,14,6,10,4,7,22}, + {28,49,33,36,29,43,34,47,26,40,46,41,48,31,25,44,38,32,35,27,30,37,42,39,45,13,6,19,24,2,20,1,8,4,0,14,12,23,17,7,10,5,16,18,9,15,22,11,3,21}, + {29,34,30,44,27,37,48,45,39,46,32,26,43,38,25,36,41,40,42,49,47,31,28,33,35,24,10,22,0,4,11,9,20,8,16,1,23,19,3,6,17,21,14,5,7,13,2,12,15,18}, + {30,29,33,31,36,35,26,49,28,43,47,32,46,27,42,34,38,41,48,40,37,45,44,39,25,16,0,3,7,10,8,11,17,5,12,9,13,18,6,2,24,1,20,14,23,21,19,15,4,22}, + {41,34,33,42,48,36,27,32,30,45,44,40,47,25,31,43,37,49,35,46,29,26,28,39,38,21,12,8,16,0,2,9,14,3,13,19,4,20,6,22,1,17,11,23,24,18,5,15,10,7}, + {48,33,49,44,32,29,34,31,38,27,40,35,28,46,30,47,45,39,37,42,36,26,25,41,43,24,8,13,21,18,5,20,22,1,4,7,3,12,19,15,2,17,6,0,16,9,23,14,11,10}, + {32,26,41,39,45,47,42,38,27,35,28,33,49,25,36,44,43,46,29,40,34,37,30,48,31,13,24,16,5,15,17,7,0,12,9,8,18,4,1,10,19,2,6,20,11,21,23,22,3,14}, + {46,33,29,39,38,45,49,35,43,31,28,26,37,36,44,32,42,40,25,41,48,27,34,47,30,0,4,9,1,10,13,23,17,6,22,3,14,8,19,16,20,7,5,12,15,2,11,18,21,24}, + {43,28,27,40,42,34,47,29,46,38,25,32,41,45,35,48,31,44,33,36,30,26,39,49,37,20,11,7,16,6,17,5,2,13,23,14,12,19,21,15,8,4,10,9,18,3,24,1,0,22}, + {46,38,27,34,25,33,30,43,41,39,32,35,37,40,48,44,29,28,36,49,47,45,26,42,31,10,18,2,6,1,23,15,7,0,20,24,11,13,17,9,12,8,16,5,4,22,21,3,14,19}, + {39,29,44,34,49,37,35,38,41,36,27,31,42,47,43,30,33,28,46,26,40,32,45,48,25,24,20,10,3,14,21,7,0,13,23,18,15,6,22,11,16,8,12,5,2,19,1,17,9,4}, + {43,40,29,37,45,33,38,42,35,46,26,41,36,34,28,49,48,30,25,32,44,27,31,47,39,13,23,17,0,16,8,1,19,6,20,2,12,3,14,24,22,21,7,10,4,18,9,5,11,15}, + {25,27,48,42,40,37,46,28,34,44,39,33,31,29,45,49,35,38,30,32,43,41,36,47,26,24,20,22,21,6,9,0,3,13,16,15,10,2,4,18,23,17,8,14,7,11,5,12,19,1}, + {46,40,29,49,30,33,43,39,44,35,27,31,36,41,38,47,45,34,48,42,32,37,25,28,26,24,2,14,4,21,12,6,1,0,13,7,9,8,11,23,15,19,17,16,20,5,10,22,3,18}, + {33,49,25,45,44,41,38,35,31,46,26,36,43,29,37,42,48,32,40,34,28,47,30,39,27,22,12,3,24,9,5,8,19,14,20,1,11,23,4,6,13,7,2,0,15,21,18,16,10,17}, + {33,25,29,48,34,41,37,43,36,45,49,31,39,42,32,46,35,27,30,44,47,40,38,28,26,2,5,3,0,7,20,9,21,16,11,4,13,12,6,8,22,14,10,19,17,1,24,18,15,23}, + {47,44,42,36,30,48,28,41,29,33,43,49,39,26,32,27,34,46,40,38,37,31,35,45,25,23,11,18,24,22,13,5,10,3,12,21,1,16,9,15,20,14,8,19,7,6,0,4,2,17}, + {42,30,36,26,45,33,32,40,35,47,46,43,28,41,39,34,38,44,37,49,29,48,27,31,25,24,1,23,13,18,5,19,21,15,4,7,6,0,22,16,3,14,10,17,20,11,8,2,12,9}, + {37,48,34,32,43,39,44,28,25,29,38,42,36,46,35,31,26,41,49,45,33,47,40,27,30,17,6,15,21,12,9,2,5,19,1,7,13,22,14,10,4,23,16,24,0,20,8,11,18,3}, + {35,43,27,26,41,30,34,44,47,37,49,48,33,46,39,32,36,40,31,42,25,28,38,45,29,12,9,4,14,8,15,19,22,3,11,6,1,24,13,5,23,20,0,2,17,10,18,16,21,7}, + {37,49,27,38,47,45,26,41,36,44,35,29,31,34,25,33,40,28,32,48,30,39,46,43,42,24,9,13,17,16,2,20,22,6,14,0,5,12,7,15,10,19,4,23,1,11,21,8,3,18}, + {48,46,28,26,30,36,49,45,35,39,33,42,34,40,32,31,44,47,29,43,27,38,37,41,25,21,9,23,15,19,8,16,14,24,2,4,17,12,18,3,11,10,0,5,6,1,22,7,13,20}, + {44,36,31,34,43,26,45,40,48,33,38,42,27,25,30,39,47,28,41,37,49,46,32,29,35,11,0,10,16,1,7,13,20,2,8,21,18,15,5,12,24,4,3,6,9,22,19,17,14,23}, + {44,46,43,37,34,45,26,40,33,49,35,29,47,32,27,31,38,41,30,28,42,39,36,48,25,9,4,8,3,14,11,7,17,19,6,10,23,13,16,22,20,0,15,2,1,5,24,21,18,12}, + {36,47,30,41,32,40,44,43,28,34,45,37,27,26,29,46,39,48,38,42,33,49,35,25,31,5,21,8,24,17,16,23,6,15,20,3,0,2,14,7,22,18,11,19,4,1,13,10,9,12}, + {28,45,33,48,47,38,46,32,37,42,31,41,49,26,29,27,30,40,35,39,34,36,44,43,25,12,14,20,19,10,13,23,17,3,11,0,6,4,18,22,8,2,9,21,5,7,24,1,15,16}, + {33,38,35,25,41,36,44,31,49,28,48,27,43,46,30,26,42,40,29,39,34,32,37,47,45,10,24,1,19,12,11,6,0,23,20,8,22,21,4,16,5,13,2,14,17,9,15,18,3,7}, + {32,48,30,39,46,25,43,31,34,40,29,28,35,26,27,33,37,44,42,47,45,36,38,41,49,5,11,20,1,10,18,22,12,0,7,9,14,16,6,2,4,8,17,3,21,23,15,13,24,19}, + {28,27,38,30,45,44,32,37,29,26,35,31,49,42,48,46,40,33,41,34,39,43,25,47,36,13,14,12,23,3,11,7,5,19,2,10,20,18,1,9,0,22,8,16,15,4,6,21,24,17}, + {40,48,32,35,25,38,30,26,34,42,44,36,49,39,27,47,43,28,46,29,45,37,41,33,31,20,24,8,16,12,9,0,13,21,19,2,1,15,5,17,23,14,18,7,4,6,10,3,22,11}, + {35,33,36,47,45,39,29,40,30,43,27,46,31,34,32,48,37,28,49,38,42,41,26,44,25,10,14,21,17,19,22,3,15,24,11,7,9,2,18,20,5,8,0,4,13,16,1,6,23,12}, + {27,33,45,41,31,39,28,30,46,25,34,32,43,29,48,26,38,49,40,42,37,36,47,35,44,2,21,17,15,12,0,11,16,1,5,9,19,8,23,13,18,22,6,4,24,20,14,10,3,7}, + } + }, +}; diff --git a/hits_cpp/shufflesGenerator.cpp b/hits_cpp/shufflesGenerator.cpp new file mode 100644 index 0000000..1070458 --- /dev/null +++ b/hits_cpp/shufflesGenerator.cpp @@ -0,0 +1,48 @@ +#include +#include + +#include "shufflesGenerator.hpp" +#include "shufflePatterns.hpp" + +#include "memes.hpp" + +using namespace std; + +Memes loadMemes(string memePath, int limit, bool verbose); + +vector* getPattern(int length) { + auto iter = _shuffle_sequences.find(length); + if (iter == _shuffle_sequences.end()) { + return nullptr; + } + auto pattern = &iter->second[1]; + return pattern; +} + +MemeShuffler getShuffler(Meme& meme, int maxPatterns) { + auto length = meme.getRows().size(); + auto iter = _shuffle_sequences.find(length); + if (iter == _shuffle_sequences.end()) { + ShufflePatterns patterns; + return MemeShuffler(meme, -1, patterns); + } + return MemeShuffler(meme, maxPatterns, iter->second); +} + +// int main() { +// // cout << "hello" << endl; +// // auto pattern = getPattern(7); +// // cout << (*pattern)[0] << endl; + +// Memes memes = loadMemes("../output/mock_small_pval/analysis/motif_inference/17b/meme.txt", 0, false); +// auto memesIter = memes.getMemes().begin(); +// cout << memesIter->first << endl; +// auto meme = memesIter->second; +// cout << "rows: " << meme.getRows().size() << ", a: " << meme.getALength() << endl; +// auto shuffler = getShuffler(meme, 5); +// while (shuffler.next()) { +// auto newMeme = shuffler.generate(); +// cout << newMeme.getMotif() << endl; +// cout << newMeme.getRows().size() << endl; +// } +// } \ No newline at end of file diff --git a/hits_cpp/shufflesGenerator.hpp b/hits_cpp/shufflesGenerator.hpp new file mode 100644 index 0000000..ca64320 --- /dev/null +++ b/hits_cpp/shufflesGenerator.hpp @@ -0,0 +1,62 @@ +#pragma once + +#include "meme.hpp" +#include +using namespace std; + +class MemeShuffler { +public: + MemeShuffler(Meme& source, int maxShuffles, ShufflePatterns& patterns) : + _source(source), _patterns(patterns), _currentShuffle(-1), _lenPatterns(patterns.size()), _numPattern(-1) { + auto len = patterns.size(); + if (len < maxShuffles) { + maxShuffles = len; + } + this->_maxShuffles = maxShuffles; + } + + bool next() { + _currentShuffle++; + _numPattern++; + return hasNext(); + } + + Meme generate() { + auto meme = Meme(this->_source); + meme.getRows().clear(); + auto pattern = this->_patterns[this->_numPattern]; + auto iter = pattern.begin(); + auto end = pattern.end(); + while (iter != end) { + meme.getRows().push_back(this->_source.getRows()[*iter]); + iter++; + } + //check that we don't change place of the same consensus: + for (int i = 0; i < this->_source.getRows().size(); i++){ + auto max_source = max_element(this->_source.getRows()[i].begin(), this->_source.getRows()[i].end()) - this->_source.getRows()[i].begin(); + auto max_shuffle = max_element(meme.getRows()[i].begin(), meme.getRows()[i].end()) - meme.getRows()[i].begin(); + if (max_source == max_shuffle && (this->_lenPatterns - this->_numPattern) > (this->_maxShuffles - this->_currentShuffle)){ + _numPattern++; + return this->generate(); + } + } + return meme; + } + + bool hasNext() { + return _currentShuffle < _maxShuffles; + } + + int getMaxShuffles() { + return this->_maxShuffles; + } +private: + Meme _source; //TODO pointer? + ShufflePatterns _patterns; //TODO pointer? + int _maxShuffles; + int _currentShuffle; + int _lenPatterns; + int _numPattern; +}; + +MemeShuffler getShuffler(Meme& meme, int maxPatterns); diff --git a/hits_cpp/trim.cpp b/hits_cpp/trim.cpp new file mode 100644 index 0000000..52a97a0 --- /dev/null +++ b/hits_cpp/trim.cpp @@ -0,0 +1,18 @@ +#include "trim.hpp" + +string& ltrim(string& str, const string& chars) +{ + str.erase(0, str.find_first_not_of(chars)); + return str; +} + +string& rtrim(string& str, const string& chars) +{ + str.erase(str.find_last_not_of(chars) + 1); + return str; +} + +string& trim(string& str, const string& chars) +{ + return ltrim(rtrim(str, chars), chars); +} \ No newline at end of file diff --git a/hits_cpp/trim.hpp b/hits_cpp/trim.hpp new file mode 100644 index 0000000..bec5139 --- /dev/null +++ b/hits_cpp/trim.hpp @@ -0,0 +1,7 @@ +#pragma once +#include + +using namespace std; +string& ltrim(string& str, const string& chars = "\t\n\v\f\r "); +string& rtrim(string& str, const string& chars = "\t\n\v\f\r "); +string& trim(string& str, const string& chars = "\t\n\v\f\r "); \ No newline at end of file diff --git a/hits_cpp/types.hpp b/hits_cpp/types.hpp new file mode 100644 index 0000000..2fc77cf --- /dev/null +++ b/hits_cpp/types.hpp @@ -0,0 +1,23 @@ +#pragma once +#include +#include +#include +#include + +// Alphabet of 20 + gap +#define MAX_MEME_COLUMNS 21 + +using namespace std; +class Meme; +typedef map MemesMap; +typedef map*> SequencesMap; +typedef map AlphabetMap; +typedef map CutoffsMap; +typedef vector> MemeRows; +typedef map SequencesCount; +typedef vector ShufflePattern; +typedef vector ShufflePatterns; +typedef map ShufflesMap; +typedef map> MemeShufflesMap; +typedef map MemeRatingMap; +typedef map SequencesRpmMap; diff --git a/mock_data/samplename2biologicalcondition.txt b/mock_data/samplename2biologicalcondition.txt old mode 100755 new mode 100644 diff --git a/model_fitting/merge_pvalues.py b/model_fitting/merge_pvalues.py index aa437ea..63e1621 100644 --- a/model_fitting/merge_pvalues.py +++ b/model_fitting/merge_pvalues.py @@ -3,8 +3,10 @@ import sys if os.path.exists('/groups/pupko/orenavr2/'): src_dir = '/groups/pupko/orenavr2/igomeProfilingPipeline/src' -else: +elif os.path.exists('/Users/Oren/Dropbox/Projects/'): src_dir = '/Users/Oren/Dropbox/Projects/gershoni/src' +else: + src_dir = '.' sys.path.insert(0, src_dir) from auxiliaries.pipeline_auxiliaries import verify_file_is_not_empty, load_table_to_dict @@ -22,56 +24,90 @@ def get_consensus_sequences_from_meme(meme_path): return result -def get_results(path): - pvalues = [] - hits = [] +def get_results_pval(consensusesValues, sample_name, path): + # pvalues = [] + # hits = [] with open(path) as f: for line in f: if line.startswith('##'): - # "## PSSM_name p_Value True_Hits: num_of_hits" + # "## PSSM_name p_Value Hits: num_of_hits" continue - # "CLKGASFLAC_17b_clusterRank_0_uniqueMembers_339_clusterSize_2659173.71.faa\t0.01\tTrue_Hits: 118" + # "CGGLKGAPFLAC_17b_clusterRank_0000_uniqueMembers_top100_clusterSize_34471.00.faa\t0.01\tHits: 767864\tUse_RPM: 1" + motif = line.split('_')[0] line_tokens = line.split('\t') - # consensus.append(line_tokens[0].split('_')[0]) - pvalues.append(line_tokens[1]) - hits.append(line_tokens[2].split()[-1]) + pvalue = line_tokens[1] + hits = line_tokens[2].split()[-1] - return pvalues, hits + if motif not in consensusesValues: + motifSamples = {} + consensusesValues[motif] = motifSamples + else: + motifSamples = consensusesValues[motif] + motifSamples[sample_name] = { 'hits': hits, 'pvalue': pvalue } -def aggregate_pvalues_results(meme_path, scanning_results_dir_path, bc, samplename2biologicalcondition_path, - aggregated_pvalues_path, aggregated_hits_path, done_path, argv='no_argv'): - samplename2biologicalcondition = load_table_to_dict(samplename2biologicalcondition_path, - 'Barcode {} belongs to more than one sample_name!!') +def get_results_shuffles(consensusesValues, sample_name, path): + with open(path) as f: + for line in f: + # first line get motif - MOTIF CDWFEQYGLRLR_17b_clusterRank_0003_uniqueMembers_51_clusterSize_62065.05.faa + motif = line.split()[1].split('_')[0] + # secand line get hits - HITS 1043 + hits = f.readline().split()[1] + # third line skip + line3 = f.readline() + # four line get values - RANK 1.00 + pvalue = f.readline().split()[1] + # five line skip + line5 = f.readline() + # six line skip + line6 = f.readline() + + if motif not in consensusesValues: + motifSamples = {} + consensusesValues[motif] = motifSamples + else: + motifSamples = consensusesValues[motif] + + motifSamples[sample_name] = { 'hits': hits, 'pvalue': pvalue } + +def aggregate_values_results(meme_path, scanning_results_dir_path, bc, samplename2biologicalcondition_path, + aggregated_pvalues_path, aggregated_hits_path, done_path, rank_method, bc_sample_names, argv='no_argv'): + if not bc_sample_names: + samplename2biologicalcondition = load_table_to_dict(samplename2biologicalcondition_path, + 'Barcode {} belongs to more than one sample_name!!') + bc_sample_names = [sample for sample in samplename2biologicalcondition if samplename2biologicalcondition[sample] == bc] + all_consensuses = get_consensus_sequences_from_meme(meme_path) + samples = set() + # consensuses => samples => hits, pvals + consensusesValues = {} + for file_name in sorted(os.listdir(scanning_results_dir_path)): + sample_name = file_name.split('_peptides')[0] + samples.add(sample_name) + if rank_method == 'pval': + get_results_pval(consensusesValues, sample_name, os.path.join(scanning_results_dir_path, file_name)) + else: # rank_method == 'shuffles' + get_results_shuffles(consensusesValues, sample_name, os.path.join(scanning_results_dir_path, file_name)) pvalues_f = open(aggregated_pvalues_path, 'w') hits_f = open(aggregated_hits_path, 'w') - #header - pvalues_result = hits_result = f'sample_name,label,{",".join(all_consensuses)}' - for file_name in sorted(os.listdir(scanning_results_dir_path)): - if file_name.endswith('100.txt'): - raise TypeError # why? - - if file_name.endswith('00.txt'): - # next sample is starting - pvalues_f.write(f'{pvalues_result.rstrip(",")}\n') - hits_f.write(f'{hits_result.rstrip(",")}\n') - sample_name = file_name.split('_peptides')[0] - if bc in sample_name: - label = samplename2biologicalcondition[sample_name] - else: - label = 'other' - pvalues_result = hits_result = f'{sample_name},{label},' - - pvalues, hits = get_results(os.path.join(scanning_results_dir_path, file_name)) - pvalues_result += ','.join(pvalues) + ',' - hits_result += ','.join(hits) + ',' - - pvalues_f.write(f'{pvalues_result.rstrip(",")}\n') - hits_f.write(f'{hits_result.rstrip(",")}\n') + # header + header = f'sample_name,label,{",".join(all_consensuses).rstrip(",")}\n' + pvalues_f.write(header) + hits_f.write(header) + + for sample in sorted(samples): + label = bc if sample in bc_sample_names else 'other' + pvalues_f.write(f'{sample},{label}') + hits_f.write(f'{sample},{label}') + for consensus in all_consensuses: + values = consensusesValues[consensus][sample] + pvalues_f.write(f',{values["pvalue"]}') + hits_f.write(f',{values["hits"]}') + pvalues_f.write('\n') + hits_f.write('\n') pvalues_f.close() hits_f.close() @@ -98,7 +134,9 @@ def aggregate_pvalues_results(meme_path, scanning_results_dir_path, bc, samplena parser.add_argument('aggregated_pvalues_path', help='A path to which the Pvalues table will be written to') parser.add_argument('aggregated_hits_path', help='A path to which the hits table will be written to') parser.add_argument('samplename2biologicalcondition_path', type=str, help='A path to the sample name to biological condition file') - parser.add_argument('done_file_path', help='A path to a file that signals that the script finished running successfully.') + parser.add_argument('done_file_path', help='A path to a file that signals that the script finished running successfully') + parser.add_argument('--rank_method', choices=['pval', 'shuffles'], default='shuffles', help='Motifs ranking method') + parser.add_argument('--bc_sample_names', type=str, help='Samples that are for the label bc, seperate by comma') parser.add_argument('-v', '--verbose', action='store_true', help='Increase output verbosity') args = parser.parse_args() @@ -108,9 +146,7 @@ def aggregate_pvalues_results(meme_path, scanning_results_dir_path, bc, samplena logging.basicConfig(level=logging.INFO) logger = logging.getLogger('main') - aggregate_pvalues_results(args.meme_path, args.scanning_results_dir_path, - args.biological_condition, args.samplename2biologicalcondition_path, - args.aggregated_pvalues_path, args.aggregated_hits_path, - args.done_file_path, argv=sys.argv) - - + aggregate_values_results(args.meme_path, args.scanning_results_dir_path, + args.biological_condition, args.samplename2biologicalcondition_path, + args.aggregated_pvalues_path, args.aggregated_hits_path, + args.done_file_path, args.rank_method, args.bc_sample_names, sys.argv) diff --git a/model_fitting/module_wraper.py b/model_fitting/module_wraper.py index d6d1e9e..583d7e8 100644 --- a/model_fitting/module_wraper.py +++ b/model_fitting/module_wraper.py @@ -1,171 +1,476 @@ -import datetime -import os -import sys -if os.path.exists('/groups/pupko/orenavr2/'): - src_dir = '/groups/pupko/orenavr2/igomeProfilingPipeline/src' -else: - src_dir = '/Users/Oren/Dropbox/Projects/gershoni/src' -sys.path.insert(0, src_dir) - -from auxiliaries.pipeline_auxiliaries import * - - - -def build_classifier(first_phase_output_path, motif_inference_output_path, - classification_output_path, logs_dir, samplename2biologicalcondition_path, - fitting_done_path, number_of_random_pssms, queue_name, verbose, error_path, argv): - - os.makedirs(classification_output_path, exist_ok=True) - os.makedirs(logs_dir, exist_ok=True) - - if os.path.exists(fitting_done_path): - logger.info(f'{datetime.datetime.now()}: skipping motif_inference step ({fitting_done_path} already exists)') - return - - samplename2biologicalcondition = load_table_to_dict(samplename2biologicalcondition_path, 'Barcode {} belongs to more than one sample_name!!') - sample_names = sorted(samplename2biologicalcondition) - biological_conditions = sorted(set(samplename2biologicalcondition.values())) - - for bc in biological_conditions: - bc_dir_path = os.path.join(classification_output_path, bc) - os.makedirs(bc_dir_path, exist_ok=True) - scanning_dir_path = os.path.join(bc_dir_path, 'scanning') - os.makedirs(scanning_dir_path, exist_ok=True) - - - # compute scanning scores (hits and pvalues) - logger.info('_'*100) - logger.info(f'{datetime.datetime.now()}: upper casing all sequences in the faa files') - script_name = 'scan_peptides_vs_motifs.py' - num_of_expected_results = 0 - num_of_cmds_per_job = 5 - all_cmds_params = [] # a list of lists. Each sublist contain different parameters set for the same script to reduce the total number of jobs - for bc in biological_conditions: - # get current biological condition (splitted) motifs folder - memes_path = os.path.join(motif_inference_output_path, bc, 'memes') - cutoffs_path = os.path.join(motif_inference_output_path, bc, 'cutoffs') - - for file_name in sorted(os.listdir(memes_path)): - # extract each split of the motifs to scan peptides against it - meme_file_path = os.path.join(memes_path, file_name) - cutoffs_file_path = os.path.join(cutoffs_path, file_name) - for sample_name in sample_names: - sample_first_phase_output_path = os.path.join(first_phase_output_path, sample_name) - faa_file_path = get_faa_file_name_from_path(sample_first_phase_output_path) - output_path = os.path.join(classification_output_path, bc, 'scanning', - f'{sample_name}_peptides_vs_{bc}_motifs_{os.path.splitext(file_name)[0]}.txt') - done_path = os.path.join(logs_dir, f'{sample_name}_peptides_vs_{bc}_motifs_{os.path.splitext(file_name)[0]}_done_scan.txt') - all_cmds_params.append([meme_file_path, cutoffs_file_path, faa_file_path, - str(number_of_random_pssms), output_path, done_path]) - - for i in range(0, len(all_cmds_params), num_of_cmds_per_job): - current_batch = all_cmds_params[i: i + num_of_cmds_per_job] - done_file_name = os.path.split(current_batch[0][-1])[-1] - name_tokens = done_file_name.split('_peptides_vs_') - logger.info(name_tokens) - sample_name = name_tokens[0] - bc = name_tokens[1].split('_motifs_')[0] - split_num = name_tokens[1].split('_motifs_')[1].split('_done_')[0] - assert sample_name in sample_names, f'Sample {sample_name} not in sample names list:\n{sample_names}' - assert bc in biological_conditions, f'Biological condition {bc} not in bc names list:\n{biological_conditions}' - cmd = submit_pipeline_step(f'{src_dir}/model_fitting/{script_name}', current_batch, - logs_dir, f'{sample_name}_vs_{bc}_scan_{split_num}', queue_name, verbose) - num_of_expected_results += len(current_batch) - - wait_for_results(script_name, logs_dir, num_of_expected_results, example_cmd=cmd, - error_file_path=error_path, suffix='_done_scan.txt') - - - # aggragate scanning scores (hits and pvalues) - logger.info('_'*100) - logger.info(f'{datetime.datetime.now()}: aggregating scores') - script_name = 'merge_pvalues.py' - num_of_expected_results = 0 - all_cmds_params = [] # a list of lists. Each sublist contain different parameters set for the same script to reduce the total number of jobs - for bc in biological_conditions: - meme_path = os.path.join(motif_inference_output_path, bc, 'meme.txt') - scanning_dir_path = os.path.join(classification_output_path, bc, 'scanning') - aggregated_pvalues_path = os.path.join(classification_output_path, bc, f'{bc}_pvalues.csv') - aggregated_hits_path = os.path.join(classification_output_path, bc, f'{bc}_hits.csv') - done_path = os.path.join(logs_dir, f'{bc}_done_aggregate_scores.txt') - all_cmds_params.append([meme_path, scanning_dir_path, bc, aggregated_pvalues_path, - aggregated_hits_path, samplename2biologicalcondition_path, done_path]) - - for cmds_params, bc in zip(all_cmds_params, biological_conditions): - cmd = submit_pipeline_step(f'{src_dir}/model_fitting/{script_name}', - [cmds_params], - logs_dir, f'{bc}_aggregate_scores', - queue_name, verbose) - num_of_expected_results += 1 # a single job for each biological condition - - wait_for_results(script_name, logs_dir, num_of_expected_results, example_cmd=cmd, - error_file_path=error_path, suffix='_done_aggregate_scores.txt') - - - # fitting a random forest model (hits and pvalues) - logger.info('_'*100) - logger.info(f'{datetime.datetime.now()}: fitting model') - script_name = 'random_forest.py' - num_of_expected_results = 0 - all_cmds_params = [] # a list of lists. Each sublist contain different parameters set for the same script to reduce the total number of jobs - for bc in biological_conditions: - aggregated_pvalues_path = os.path.join(classification_output_path, bc, f'{bc}_pvalues.csv') - aggregated_hits_path = os.path.join(classification_output_path, bc, f'{bc}_hits.csv') - done_path = os.path.join(logs_dir, f'{bc}_done_fitting.txt') - all_cmds_params.append([aggregated_pvalues_path, done_path, - f'!@#python3 {src_dir}/model_fitting/{script_name}', - aggregated_hits_path, done_path]) - - for cmds_params, bc in zip(all_cmds_params, biological_conditions): - cmd = submit_pipeline_step(f'{src_dir}/model_fitting/{script_name}', - [cmds_params], - logs_dir, f'{bc}_model', - queue_name, verbose) - num_of_expected_results += 1 # a single job for each biological condition - - wait_for_results(script_name, logs_dir, num_of_expected_results, example_cmd=cmd, - error_file_path=error_path, suffix='_done_fitting.txt') - - - # TODO: fix this bug with a GENERAL WRAPPER done_path - # wait_for_results(script_name, num_of_expected_results) - with open(fitting_done_path, 'w') as f: - f.write(' '.join(argv) + '\n') - - -def get_faa_file_name_from_path(path): - file_name = [file for file in os.listdir(path) if file.endswith('faa')][0] - return os.path.join(path, file_name) - - -if __name__ == '__main__': - print(f'Starting {sys.argv[0]}. Executed command is:\n{" ".join(sys.argv)}') - - import argparse - parser = argparse.ArgumentParser() - parser.add_argument('parsed_fastq_results', type=str, help='A path in which each subfolder corresponds to a samplename and contains a collapsed faa file') - parser.add_argument('motif_inference_results', type=str, help='A path in which there is a subfolder for each bc. ' - 'In each such subfolder there is a memes folder and a cutoffs folder.') - parser.add_argument('classification_output_path', type=str, help='output folder') - parser.add_argument('logs_dir', type=str, help='logs folder') - parser.add_argument('samplename2biologicalcondition_path', type=str, help='A path to the sample name to biological condition file') - parser.add_argument('number_of_random_pssms', default=100, type=int, help='Number of pssm permutations') - parser.add_argument('done_file_path', help='A path to a file that signals that the module finished running successfully.') - - parser.add_argument('--error_path', type=str, help='a file in which errors will be written to') - parser.add_argument('-q', '--queue', default='pupkoweb', type=str, help='a queue to which the jobs will be submitted') - parser.add_argument('-v', '--verbose', action='store_true', help='Increase output verbosity') - args = parser.parse_args() - - import logging - if args.verbose: - logging.basicConfig(level=logging.DEBUG) - else: - logging.basicConfig(level=logging.WARNING) - logger = logging.getLogger('main') - - error_path = args.error_path if args.error_path else os.path.join(args.parsed_fastq_results, 'error.txt') - - build_classifier(args.parsed_fastq_results, args.motif_inference_results, args.classification_output_path, - args.logs_dir, args.samplename2biologicalcondition_path, args.done_file_path, - args.number_of_random_pssms, args.queue, True if args.verbose else False, error_path, sys.argv) +import datetime +import os +import sys +import json +if os.path.exists('/groups/pupko/orenavr2/'): + src_dir = '/groups/pupko/orenavr2/igomeProfilingPipeline/src' +elif os.path.exists('/Users/Oren/Dropbox/Projects/'): + src_dir = '/Users/Oren/Dropbox/Projects/gershoni/src' +else: + src_dir = '.' +sys.path.insert(0, src_dir) + +from auxiliaries.pipeline_auxiliaries import load_table_to_dict, submit_pipeline_step, run_step_locally,\ + wait_for_results, process_params +from auxiliaries.stop_machine_aws import stop_machines +from auxiliaries.validation_files import is_input_files_valid + + +map_names_command_line = { + "parsed_fastq_results" : "reads_path", + "motif_inference_results" : "motifs_path", + "classification_output_path" : "model_path", + "logs_dir" : "logs_dir", + "samplename2biologicalcondition_path" : "sample2bc", + "number_of_random_pssms" : "num_of_random_pssms", + "done_file_path" : "done_file_path", + "cross_experiments_config" : "cross_experiments_config", + "check_files_valid" : "check_files_valid", + "stop_before_random_forest" : "stop_before_random_forest", + "num_of_random_configurations_to_sample" : "num_of_random_configurations_to_sample", + "is_run_random_forest_per_bc_sequentially" : "is_run_random_forest_per_bc_sequentially", + "number_parallel_random_forest" : "number_parallel_rf", + "min_value_error_random_forest" : "min_value_error_rf", + "rank_method" : "rank_method", + "tfidf_method" : "tfidf_method", + "tfidf_factor" : "tfidf_factor", + "shuffles" : "shuffles", + "shuffles_percent" : "shuffles_percent", + "shuffles_digits" : "shuffles_digits", + "cv_num_of_splits" : "cv_num_of_splits", + "seed_random_forest" : "rf_seed", + "random_forest_seed_configurations" : "rf_seed_configurations", + "stop_machines" : "stop_machines_flag", + "type_machines_to_stop" : "type_machines_to_stop", + "name_machines_to_stop" : "name_machines_to_stop", + "no_rpm_factor": "no_rpm_factor", + "no_output_sequences_scanning": "no_output_sequences_scanning", + "no_use_rpm_faa_scanning": "no_use_rpm_faa_scanning", + "filter_positive_motifs": "filter_positive_motifs", + "invalid_mix_positive_motifs": "invalid_mix_positive_motifs", + "threshold_mean_positive_motifs": "threshold_mean_positive_motifs", + "threshold_std_positive_motifs": "threshold_std_positive_motifs", + "threshold_median_positive_motifs": "threshold_median_positive_motifs", + "min_max_difference_positive_motifs": "min_max_difference_positive_motifs", + "normalize_factor_positive_motifs": "normalize_factor_positive_motifs", + "normalize_method_hits_positive_motifs": "normalize_method_hits_positive_motifs", + "normalize_section_positive_motifs": "normalize_section_positive_motifs", + "fixed_min_positive_motifs": "fixed_min_positive_motifs", + "fixed_max_positive_motifs": "fixed_max_positive_motifs", + "queue" : "queue", + "verbose" : "verbose", + "error_path" : "error_path", + "mapitope" : "mapitope" +} + + +def get_sample_and_bc_from_sample2bc(multi_experiments_dict, exp_name): + dict_sample2bc = multi_experiments_dict['runs'][exp_name]['sample2bc'] + sample_names = [] + biological_conditions = [] + samplename2biologicalcondition_path = '' + if "motifs&samples" in dict_sample2bc: + samplename2biologicalcondition = load_table_to_dict(dict_sample2bc['motifs&samples'], 'Barcode {} belongs to more than one sample_name!!') + sample_names = sorted(samplename2biologicalcondition) + biological_conditions = sorted(set(samplename2biologicalcondition.values())) + samplename2biologicalcondition_path = dict_sample2bc['motifs&samples'] + else: + if "motifs" not in dict_sample2bc or "samples" not in dict_sample2bc: + logger.info('Missing file samplenames2biologicalcondition of motifs and samples') + return sample_names, biological_conditions, samplename2biologicalcondition_path + else: + samplename2biologicalcondition = load_table_to_dict(dict_sample2bc['motifs'], 'Barcode {} belongs to more than one sample_name!!') + biological_conditions = sorted(set(samplename2biologicalcondition.values())) + samplename2biologicalcondition_path = dict_sample2bc['motifs'] + samplename2biologicalcondition = load_table_to_dict(dict_sample2bc['samples'], 'Barcode {} belongs to more than one sample_name!!') + sample_names = sorted(samplename2biologicalcondition) + + return sample_names, biological_conditions, samplename2biologicalcondition_path + + +def repeat_items(list): + output = [] + for x in list: + output.append(x) + output.append(x) + return output + + +def build_classifier(reads_path, motifs_path, model_path, logs_dir, sample2bc, num_of_random_pssms, + done_file_path, check_files_valid, cross_experiments_config, stop_before_random_forest, + is_run_random_forest_per_bc_sequentially, num_of_random_configurations_to_sample, number_parallel_rf, + min_value_error_rf, rank_method, tfidf_method, tfidf_factor, shuffles, shuffles_percent, shuffles_digits, + cv_num_of_splits, rf_seed, rf_seed_configurations, no_rpm_factor, no_use_rpm_faa_scanning, + stop_machines_flag, type_machines_to_stop, name_machines_to_stop, no_output_sequences_scanning, + filter_positive_motifs, invalid_mix_positive_motifs, threshold_mean_positive_motifs, + threshold_std_positive_motifs, threshold_median_positive_motifs, min_max_difference_positive_motifs, + normalize_factor_positive_motifs, normalize_method_hits_positive_motifs, normalize_section_positive_motifs, + fixed_min_positive_motifs, fixed_max_positive_motifs, queue, verbose, error_path, mapitope, exp_name, argv): + + if exp_name: + logger.info(f'{datetime.datetime.now()}: Start model fitting step for experiments {exp_name}') + + error_path = error_path or os.path.join(model_path, 'error.txt') + + multi_experiments_dict = {} + if cross_experiments_config: + multi_experiments_dict = json.load(open(cross_experiments_config)) + + all_sample2bc = [] + if check_files_valid: + if multi_experiments_dict: + all_sample2bc = multi_experiments_dict['runs'][exp_name]['sample2bc'].values() + else: + all_sample2bc = [sample2bc] + for sample2bc_path in all_sample2bc: + if not is_input_files_valid(samplename2biologicalcondition_path=sample2bc_path, barcode2samplename_path='', logger=logger): + return + + use_merge_pvalues = rank_method in ['pval','shuffles'] + + os.makedirs(model_path, exist_ok=True) + os.makedirs(logs_dir, exist_ok=True) + + if os.path.exists(done_file_path): + logger.info(f'{datetime.datetime.now()}: skipping model_fitting step ({done_file_path} already exists)') + return + + + sample_names = [] + biological_conditions = [] + if multi_experiments_dict: + sample_names, biological_conditions, sample2bc = get_sample_and_bc_from_sample2bc(multi_experiments_dict, exp_name) + else: + samplename2biologicalcondition = load_table_to_dict(sample2bc, 'Barcode {} belongs to more than one sample_name!!') + sample_names = sorted(samplename2biologicalcondition) + biological_conditions = sorted(set(samplename2biologicalcondition.values())) + + for bc in biological_conditions: + bc_dir_path = os.path.join(model_path, bc) + os.makedirs(bc_dir_path, exist_ok=True) + scanning_dir_path = os.path.join(bc_dir_path, 'scanning') + os.makedirs(scanning_dir_path, exist_ok=True) + scanning_dir_path = os.path.join(bc_dir_path, 'sequences_hits_scanning') + os.makedirs(scanning_dir_path, exist_ok=True) + + # compute scanning scores (hits and values) + logger.info('_'*100) + logger.info(f'{datetime.datetime.now()}: scanning peptides vs motifs (hits and values)') + script_name = 'scan_peptides_vs_motifs.py' + num_of_expected_results = 0 + num_of_cmds_per_job = 4 + all_cmds_params = [] # a list of lists. Each sublist contain different parameters set for the same script to reduce the total number of jobs + for bc in biological_conditions: + # get current biological condition (splitted) motifs folder + memes_path = os.path.join(motifs_path, bc, 'memes') + cutoffs_path = os.path.join(motifs_path, bc, 'cutoffs') + + for file_name in sorted(os.listdir(memes_path)): + # extract each split of the motifs to scan peptides against it + meme_file_path = os.path.join(memes_path, file_name) + cutoffs_file_path = os.path.join(cutoffs_path, file_name) + for sample_name in sample_names: + sample_first_phase_output_path = os.path.join(reads_path, sample_name) + faa_file_path = get_faa_file_name_from_path(sample_first_phase_output_path, mapitope, no_use_rpm_faa_scanning) + output_path = os.path.join(model_path, bc, 'scanning', + f'{sample_name}_peptides_vs_{bc}_motifs_{os.path.splitext(file_name)[0]}.txt') + done_path = os.path.join(logs_dir, f'{sample_name}_peptides_vs_{bc}_motifs_{os.path.splitext(file_name)[0]}_done_scan.txt') + if not os.path.exists(done_path): + cmd = [meme_file_path, cutoffs_file_path, faa_file_path, rank_method, str(num_of_random_pssms), output_path, done_path, + '--no_rpm_factor' if no_rpm_factor else '', '--no_use_rpm_faa_scanning' if no_use_rpm_faa_scanning else ''] + if rank_method == 'shuffles': + cmd += ['--shuffles', shuffles] + cmd += ['--shuffles_percent', shuffles_percent, '--shuffles_digits', shuffles_digits] + if no_output_sequences_scanning: + cmd += ['--no_output_sequences_scanning'] + else: + sequnces_hits_path = os.path.join(model_path, bc, 'sequences_hits_scanning', + f'{sample_name}_peptides_vs_{bc}_motifs_{os.path.splitext(file_name)[0]}.txt') + cmd += ['--sequence_hit_motif_path', sequnces_hits_path] + all_cmds_params.append(cmd) + else: + logger.debug(f'skipping scan as {done_path} found') + num_of_expected_results += 1 + + if len(all_cmds_params) > 0: + for i in range(0, len(all_cmds_params), num_of_cmds_per_job): + current_batch = all_cmds_params[i: i + num_of_cmds_per_job] + done_path_index = 6 + done_file_name = os.path.split(current_batch[0][done_path_index])[-1] + name_tokens = done_file_name.split('_peptides_vs_') + logger.info(name_tokens) + sample_name = name_tokens[0] + bc = name_tokens[1].split('_motifs_')[0] + split_num = name_tokens[1].split('_motifs_')[1].split('_done_')[0] + assert sample_name in sample_names, f'Sample {sample_name} not in sample names list:\n{sample_names}' + assert bc in biological_conditions, f'Biological condition {bc} not in bc names list:\n{biological_conditions}' + cmd = submit_pipeline_step(f'{src_dir}/model_fitting/{script_name}', current_batch, + logs_dir, f'{sample_name}_vs_{bc}_scan_{split_num}', queue, verbose) + num_of_expected_results += len(current_batch) + + wait_for_results(script_name, logs_dir, num_of_expected_results, example_cmd=cmd, + error_file_path=error_path, suffix='_done_scan.txt') + else: + logger.info(f'Skipping scanning peptides vs motifs (hits and values), all scans found') + + get_sample_for_label = False + if multi_experiments_dict and "biological_motifs_combine" in multi_experiments_dict['runs'][exp_name]: + biological_conditions = multi_experiments_dict['runs'][exp_name]['biological_motifs_combine'].keys() + get_sample_for_label = True + + # aggregate scanning scores (hits and values) + logger.info('_'*100) + logger.info(f'{datetime.datetime.now()}: aggregating scores') + script_name = 'merge_pvalues.py' + num_of_expected_results = 0 + all_cmds_params = [] # a list of lists. Each sublist contain different parameters set for the same script to reduce the total number of jobs + for bc in biological_conditions: + meme_path = os.path.join(motifs_path, bc, 'meme.txt') + scanning_dir_path = os.path.join(model_path, bc, 'scanning') + done_path = os.path.join(logs_dir, f'{bc}_done_aggregate_scores.txt') + if not os.path.exists(done_path): + if not use_merge_pvalues: + cmds = ['--memes', meme_path, + '--bc', bc, + '--sam2bc', sample2bc, + '--scan', scanning_dir_path, + '--output', model_path, + '--method', tfidf_method, + '--factor', str(tfidf_factor), + '--done', done_path] + if rank_method == 'shuffles': + cmds.append('--rank') + all_cmds_params.append(cmds) + else: + aggregated_values_path = os.path.join(model_path, bc, f'{bc}_values.csv') + aggregated_hits_path = os.path.join(model_path, bc, f'{bc}_hits.csv') + cmd_merge = [meme_path, scanning_dir_path, bc, aggregated_values_path, + aggregated_hits_path, sample2bc, done_path, f'--rank_method {rank_method}'] + if get_sample_for_label: + bc_sample_names =','.join(multi_experiments_dict['runs'][exp_name]['biological_motifs_combine'][bc]) + cmd_merge += [f'--bc_sample_names {bc_sample_names}'] + all_cmds_params.append(cmd_merge) + else: + logger.debug(f'skipping score aggregation as {done_path} found') + num_of_expected_results += 1 + + if len(all_cmds_params) > 0: + executable = 'python' if use_merge_pvalues else None + script_path = f'{src_dir}/model_fitting/{script_name}' if use_merge_pvalues else f'{src_dir}/tfidf/tfidf' + for cmds_params, bc in zip(all_cmds_params, biological_conditions): + cmd = submit_pipeline_step(script_path,[cmds_params], + logs_dir, f'{bc}_aggregate_scores', + queue, verbose, executable=executable) + num_of_expected_results += 1 # a single job for each biological condition + + wait_for_results(script_name, logs_dir, num_of_expected_results, example_cmd=cmd, + error_file_path=error_path, suffix='_done_aggregate_scores.txt') + else: + logger.info(f'skipping aggregating scores, all scores found') + + # Remain only positive motifs in csv file + if filter_positive_motifs: + logger.info('_'*100) + logger.info(f'{datetime.datetime.now()}: positive motifs') + script_name = 'positive_motifs.py' + num_of_expected_results = 0 + all_cmds_params = [] + for bc in biological_conditions: + aggregated_values_path = os.path.join(model_path, bc, f'{bc}_values.csv') + output_values_path = os.path.join(model_path, bc, f'{bc}_values_positive_motifs') + values_done_path = os.path.join(logs_dir, f'{bc}_values_done_positive_motifs.txt') + aggregated_hits_path = os.path.join(model_path, bc, f'{bc}_hits.csv') + output_hits_path = os.path.join(model_path, bc, f'{bc}_hits_positive_motifs') + hits_done_path = os.path.join(logs_dir, f'{bc}_hits_done_positive_motifs.txt') + + value_cmd = [aggregated_values_path, output_values_path, values_done_path, + '' if no_rpm_factor else '--is_rpm_normalize', + '' if invalid_mix_positive_motifs is None else f'--invalid_mix {invalid_mix_positive_motifs}', + '' if threshold_mean_positive_motifs is None else f'--threshold_mean {threshold_mean_positive_motifs}', + '' if threshold_std_positive_motifs is None else f'--threshold_std {threshold_std_positive_motifs}', + '' if threshold_median_positive_motifs is None else f'--threshold_median {threshold_median_positive_motifs}', + f'--min_max_difference' if min_max_difference_positive_motifs else '', f'--rank_method {rank_method}', + f'--normalize_factor {normalize_factor_positive_motifs}', + f'--normalize_method_hits {normalize_method_hits_positive_motifs}', + f'--normalize_section {normalize_section_positive_motifs}', + '' if fixed_min_positive_motifs is None else f'--fixed_min {fixed_min_positive_motifs}', + '' if fixed_max_positive_motifs is None else f'--fixed_max {fixed_max_positive_motifs}'] + + hits_cmd = [aggregated_hits_path, output_hits_path, hits_done_path, + '' if no_rpm_factor else '--is_rpm_normalize', + '' if invalid_mix_positive_motifs is None else f'--invalid_mix {invalid_mix_positive_motifs}', + '' if threshold_mean_positive_motifs is None else f'--threshold_mean {threshold_mean_positive_motifs}', + '' if threshold_std_positive_motifs is None else f'--threshold_std {threshold_std_positive_motifs}', + '' if threshold_median_positive_motifs is None else f'--threshold_median {threshold_median_positive_motifs}', + f'--min_max_difference' if min_max_difference_positive_motifs else '', f'--rank_method hits', + f'--normalize_factor {normalize_factor_positive_motifs}', + f'--normalize_method_hits {normalize_method_hits_positive_motifs}', + f'--normalize_section {normalize_section_positive_motifs}', + '' if fixed_min_positive_motifs is None else f'--fixed_min {fixed_min_positive_motifs}', + '' if fixed_max_positive_motifs is None else f'--fixed_max {fixed_max_positive_motifs}'] + if not os.path.exists(values_done_path): + all_cmds_params.append(value_cmd) + else: + logger.debug(f'Skipping fitting as {values_done_path} found') + num_of_expected_results += 1 + + if not os.path.exists(hits_done_path): + all_cmds_params.append(hits_cmd) + else: + logger.debug(f'Skipping fitting as {hits_done_path} found') + num_of_expected_results += 1 + if len(all_cmds_params) > 0: + doubled_bc = repeat_items(biological_conditions) + for cmds_params, bc in zip(all_cmds_params, doubled_bc): + cmd = submit_pipeline_step(f'{src_dir}/model_fitting/{script_name}', + [cmds_params], logs_dir, f'{bc}_positive_motifs', queue, verbose) + num_of_expected_results += 1 + wait_for_results(script_name, logs_dir, num_of_expected_results, example_cmd=cmd, + error_file_path=error_path, suffix='_done_positive_motifs.txt') + else: + logger.info(f'Skipping positive_motifs, all found') + else: + logger.info(f'Skipping positive_motifs, not to user this script.') + + # fitting a random forest model (hits and values) + if not stop_before_random_forest: + logger.info('_'*100) + logger.info(f'{datetime.datetime.now()}: fitting model') + script_name = 'random_forest.py' + num_of_expected_results = 0 + all_cmds_params = [] # a list of lists. Each sublist contain different parameters set for the same script to reduce the total number of jobs + for bc in biological_conditions: + if filter_positive_motifs: + aggregated_values_path = os.path.join(model_path, bc,f'{bc}_values_positive_motifs.csv') + aggregated_hits_path = os.path.join(model_path, bc, f'{bc}_hits_positive_motifs.csv') + else: + aggregated_values_path = os.path.join(model_path, bc, f'{bc}_values.csv') + aggregated_hits_path = os.path.join(model_path, bc, f'{bc}_hits.csv') + pvalues_done_path = os.path.join(logs_dir, f'{bc}_values_done_fitting.txt') + hits_done_path = os.path.join(logs_dir, f'{bc}_hits_done_fitting.txt') + + value_cmd = [aggregated_values_path, pvalues_done_path, logs_dir, error_path, f'--num_of_configurations_to_sample {num_of_random_configurations_to_sample}', f'--cv_num_of_splits {cv_num_of_splits}', + f'--number_parallel_random_forest {number_parallel_rf}', f'--min_value_error_random_forest {min_value_error_rf}', f'--seed {rf_seed}', + f'--random_forest_seed {rf_seed_configurations}', f'--rank_method {rank_method}', f'--queue {queue}'] + hits_cmd = [aggregated_hits_path, hits_done_path, logs_dir, error_path, f'--num_of_configurations_to_sample {num_of_random_configurations_to_sample}', f'--cv_num_of_splits {cv_num_of_splits}', + f'--number_parallel_random_forest {number_parallel_rf}', f'--min_value_error_random_forest {min_value_error_rf}', f'--seed {rf_seed}', + f'--random_forest_seed {rf_seed_configurations}', '--rank_method hits', f'--queue {queue}'] + if not os.path.exists(pvalues_done_path): + all_cmds_params.append(value_cmd) + else: + logger.debug(f'Skipping fitting as {pvalues_done_path} found') + num_of_expected_results += 1 + + if not os.path.exists(hits_done_path): + all_cmds_params.append(hits_cmd) + else: + logger.debug(f'Skipping fitting as {hits_done_path} found') + num_of_expected_results += 1 + + if len(all_cmds_params) > 0: + doubled_bc = repeat_items(biological_conditions) + for cmds_params, bc in zip(all_cmds_params, doubled_bc): + cmd = '' + if is_run_random_forest_per_bc_sequentially: + cmd = run_step_locally(f'{src_dir}/model_fitting/{script_name}', + [cmds_params], logs_dir, f'{bc}_model', queue, verbose) + else: + cmd = submit_pipeline_step(f'{src_dir}/model_fitting/{script_name}', + [cmds_params], logs_dir, f'{bc}_model', queue, verbose) + num_of_expected_results += 1 # a single job for each biological condition + + wait_for_results(script_name, logs_dir, num_of_expected_results, example_cmd=cmd, + error_file_path=error_path, suffix='_done_fitting.txt') + else: + logger.info(f'Skipping fitting, all found') + else: + logger.info(f'Skipping fitting, stop before random forest') + + # TODO: fix this bug with a GENERAL WRAPPER done_path + # wait_for_results(script_name, num_of_expected_results) + with open(done_file_path, 'w') as f: + f.write(' '.join(argv) + '\n') + + if stop_machines_flag: + stop_machines(type_machines_to_stop, name_machines_to_stop, logger) + +def get_faa_file_name_from_path(path, use_mapitope, no_use_rpm_faa_scanning): + for file_name in os.listdir(path): + if file_name.endswith('faa') and ('unique' not in file_name) == no_use_rpm_faa_scanning and ('mapitope' in file_name) == use_mapitope: + file_name = file_name + break + return os.path.join(path, file_name) + + +if __name__ == '__main__': + print(f'Starting {sys.argv[0]}. Executed command is:\n{" ".join(sys.argv)}') + + import argparse + parser = argparse.ArgumentParser() + parser.add_argument('parsed_fastq_results', type=str, help='A path in which each subfolder corresponds to a samplename and contains a collapsed faa file') + parser.add_argument('motif_inference_results', type=str, help='A path in which there is a subfolder for each bc ' + 'In each such subfolder there is a memes folder and a cutoffs folder') + parser.add_argument('classification_output_path', type=str, help='Output folder') + parser.add_argument('logs_dir', type=str, help='logs folder') + parser.add_argument('samplename2biologicalcondition_path', type=str, help='A path to the sample name to biological condition file') + parser.add_argument('number_of_random_pssms', default=100, type=int, help='Number of pssm permutations') + parser.add_argument('done_file_path', type=str, help='A path to a file that signals that the module finished running successfully') + + parser.add_argument('--cross_experiments_config', type=str, help='Configuration file to run cross expiremets at model fitting phase') + parser.add_argument('--check_files_valid', action='store_true', help='Need to check the validation of the files (samplename2biologicalcondition_path / barcode2samplenaem)') + parser.add_argument('--stop_before_random_forest', action='store_true', help='A boolean flag for mark if we need to run the random forest') + parser.add_argument('--is_run_random_forest_per_bc_sequentially', action='store_true', help='Set the flag to true when number of cores is less than number of BC X 2 (hit and value), otherwise it will run all the BC parallel (on the same time)') + parser.add_argument('--num_of_random_configurations_to_sample', default=100, type=int, help='How many random configurations of hyperparameters should be sampled?') + parser.add_argument('--number_parallel_random_forest', default=20, type=int, help='How many random forest configurations to run in parallel') + parser.add_argument('--min_value_error_random_forest', default=0.0, type=float, help='A random forest model error value for convergence allowing to stop early') + parser.add_argument('--rank_method', choices=['pval', 'tfidf', 'shuffles'], default='shuffles', help='Motifs ranking method') + parser.add_argument('--tfidf_method', choices=['boolean', 'terms', 'log', 'augmented'], default='boolean', help='TF-IDF method') + parser.add_argument('--tfidf_factor', type=float, default=0.5, help='TF-IDF augmented method factor (0-1)') + parser.add_argument('--shuffles', default=10, type=int, help='Number of controlled shuffles permutations') + parser.add_argument('--shuffles_percent', default=0.2, type=float, help='Percent from shuffle with greatest number of hits (0-1)') + parser.add_argument('--shuffles_digits', default=2, type=int, help='Number of digits after the point to print in scanning files') + parser.add_argument('--cv_num_of_splits', default=2, type=int, help='How folds should be in the cross validation process? (use 0 for leave one out)') + parser.add_argument('--seed_random_forest', default=42, type=int, help='Seed number for reconstructing experiments') + parser.add_argument('--random_forest_seed_configurations', default=123, type=int, help='Random seed value for generating random forest configurations') + parser.add_argument('--stop_machines', action='store_true', help='Turn off the machines in AWS at the end of the running') + parser.add_argument('--type_machines_to_stop', default='', type=str, help='Type of machines to stop, separated by comma. Empty value means all machines. Example: t2.2xlarge,m5a.24xlarge') + parser.add_argument('--name_machines_to_stop', default='', type=str, help='Names (patterns) of machines to stop, separated by comma. Empty value means all machines. Example: worker*') + parser.add_argument('--no_rpm_factor', action='store_true', help='Disable multiplication hits by factor rpm for normalization') + parser.add_argument('--no_output_sequences_scanning', action='store_true', help='Disable storing the output sequences that had hits') + parser.add_argument('--no_use_rpm_faa_scanning', action='store_true', help='Disable performance of scanning script with unique rpm faa file') + parser.add_argument('--filter_positive_motifs', action='store_true', help='Filter only positive motifs. This is done after motifs generation and before ranking') + parser.add_argument('--invalid_mix_positive_motifs',type=str, default=None, help='Sample name considered negative. e.g. "native') + parser.add_argument('--threshold_mean_positive_motifs', default=None, + type=lambda x: float(x) if 0 < float(x) < 1 + else parser.error(f'The threshold of the mean diffrence should be between 0 to 1'), + help='Positive motif threshold of the difference between the mean of the BC and mean of others') + parser.add_argument('--threshold_std_positive_motifs', default=None, + type=lambda x: float(x) if 0 < float(x) < 1 + else parser.error(f'The threshold of the std diffrence should be between 0 to 1'), + help='Positive motif threshold of the difference between the std of the BC and std of others') + parser.add_argument('--threshold_median_positive_motifs', default=None, + type=lambda x: float(x) if 0 < float(x) < 1 + else parser.error(f'The threshold of the median diffrence should be between 0 to 1'), + help='Positive motif threshold of the difference between the median of the BC and median of others') + parser.add_argument('--min_max_difference_positive_motifs', action='store_true', help='Positive motif if the minmal value of bc is bigger than the maximal value of other') + parser.add_argument('--normalize_factor_positive_motifs', choices=['linear', 'log'], default='linear', help='type of factor to normalize the data. \ + If nonlinear data (values are not in the same distance) use log ,otherwise use linear') + parser.add_argument('--normalize_method_hits_positive_motifs', choices=['min_max', 'max', 'fixed_min_max'], default='min_max', + help='Method affects which values the normalization will change the data. e.g. min max bring all values into the range [0,1]') + parser.add_argument('--normalize_section_positive_motifs', choices=['per_motif','per_exp'], default='per_motif', help='Normalize the data by calculate the min and max per motif or over all the exp data') + parser.add_argument('--fixed_min_positive_motifs', type=int, default=None, help='In case of fixed_min_max for normalize_method_hits set the minimum value') + parser.add_argument('--fixed_max_positive_motifs', type=int, default=None, help='In case of fixed_min_max for normalize_method_hits set the maximum value') + parser.add_argument('--error_path', type=str, help='A file in which errors will be written to') + parser.add_argument('-q', '--queue', default='pupkoweb', type=str, help='A queue to which the jobs will be submitted') + parser.add_argument('-v', '--verbose', action='store_true', help='Increase output verbosity') + parser.add_argument('-m', '--mapitope', action='store_true', help='use mapitope encoding') + args = parser.parse_args() + + import logging + if args.verbose: + logging.basicConfig(level=logging.DEBUG) + else: + logging.basicConfig(level=logging.WARNING) + logger = logging.getLogger('main') + + process_params(args, args.cross_experiments_config, map_names_command_line, build_classifier, 'model_fitting', sys.argv) diff --git a/model_fitting/positive_motifs.py b/model_fitting/positive_motifs.py new file mode 100644 index 0000000..2b02e83 --- /dev/null +++ b/model_fitting/positive_motifs.py @@ -0,0 +1,221 @@ +import logging +import os +import sys +from matplotlib.pyplot import axis +import pandas as pd +import numpy as np +if os.path.exists('/groups/pupko/orenavr2/'): + src_dir = '/groups/pupko/orenavr2/igomeProfilingPipeline/src' +elif os.path.exists('/Users/Oren/Dropbox/Projects/'): + src_dir = '/Users/Oren/Dropbox/Projects/gershoni/src' +else: + src_dir = '.' +sys.path.insert(0, src_dir) + + +def normalize_max(df, max_value): + return (df / max_value).replace(np.nan, 0) + + +def normalize_max_min(df, max_value, min_value): + return ((df - min_value) / (max_value - min_value)).replace(np.nan, 0) + + +def normalize_log(df, rank_method): + if rank_method == 'hits': + return np.log2(df + 1) + if rank_method == 'shuffles': + return -np.log2(1 - df + 0.01) + return df + + +def min_max_fixed(df, max_value, min_value): + df[df > max_value] = max_value + df[df < min_value] = min_value + return df + + +def is_artifact(df, bio_cond, invalid_mix): + artifact_motifs = [] + motifs = list(df.columns) + motifs.remove('sample_name') + motifs.remove('label') + for motif in motifs: + bc_min = df.loc[df['label'] == bio_cond, motif].min() + mixed_samples = list(df.loc[(df['label'] == 'other') & (df[motif] >= bc_min), 'sample_name']) + is_artifact = any(invalid_mix in s for s in mixed_samples) + if is_artifact: + artifact_motifs.append(motif) + return artifact_motifs + + +def normalize(df, normalize_factor, normalize_method_hits, normaliza_section, rank_method, fixed_min, fixed_max): + # For factor log, otherwise it is linear: + if normalize_factor == 'log': + df = normalize_log(df, rank_method) + + min_motifs = df.min() + max_motifs = df.max() + min_exp = min(min_motifs) + max_exp = max(max_motifs) + # min_max per motif: + if normalize_method_hits == 'min_max' and normaliza_section == 'per_motif': + normalized_df = normalize_max_min(df, max_motifs, min_motifs) + # min_max per exp: + if normalize_method_hits =='min_max' and normaliza_section == 'per_exp': + normalized_df = normalize_max_min(df, max_exp, min_exp) + # max per motif: + if normalize_method_hits =='max' and normaliza_section == 'per_motif': + normalized_df = normalize_max(df, max_motifs) + # max per motif: + if normalize_method_hits =='max' and normaliza_section == 'per_exp': + normalized_df = normalize_max(df, max_exp) + # fixed_min_max: + if normalize_method_hits =='fixed_min_max': + if fixed_max is None: + fixed_max = max_exp + if fixed_min is None: + fixed_min = 0 + df = min_max_fixed(df, fixed_max, fixed_min) + normalized_df = normalize_max_min(df, fixed_max, fixed_min) + return normalized_df + + +def write_results(df, df_statistical, pass_motifs, output_path): + output_file_statistical = output_path + '_statistical.csv' + output_file = output_path + '.csv' + df_statistical.to_csv(output_file_statistical, float_format='%.3f') + pass_motifs.insert(0, 'label') + pass_motifs.insert(0, 'sample_name') + df_positive_motifs = df[pass_motifs] + df_positive_motifs.set_index('sample_name', inplace=True) + df_positive_motifs.to_csv(output_file) + + +def calculation(df, label): + df_mean = pd.DataFrame(df.mean()).transpose() + df_std = pd.DataFrame(df.std()).transpose() + df_median = pd.DataFrame(df.median()).transpose() + df_max = pd.DataFrame(df.max()).transpose() + df_min = pd.DataFrame(df.min()).transpose() + cal = [f'mean_{label}', f'std_{label}', f'median_{label}', f'max_{label}', f'min_{label}'] + df_calculation = pd.concat([df_mean, df_std, df_median, df_max, df_min]) + df_calculation.insert(0, 'label', cal) + df_calculation.set_index('label', inplace=True) + return df_calculation + + +def is_positive(df, motif_name, threshold_mean, threshold_std, threshold_median, min_max_difference): + if (threshold_mean is None or (df.loc['mean_BC', motif_name] - df.loc['mean_other', motif_name]) / df.loc['mean_BC', motif_name] > threshold_mean) \ + and (threshold_std is None or (df.loc['std_BC', motif_name] - df.loc['std_other', motif_name]) / df.loc['std_BC', motif_name] > threshold_std) \ + and (threshold_median is None or (df.loc['median_BC', motif_name] - df.loc['median_other', motif_name]) / df.loc['median_BC', motif_name] > threshold_median) \ + and (not min_max_difference or df.loc['min_BC', motif_name] > df.loc['max_other', motif_name]): + return True + return False + + +def find_positive_motifs(df, threshold_mean, threshold_std, threshold_median, min_max_difference, rank_method, is_rpm_normalize): + positive_motifs = [] + motifs_value = [] + for motif_name in df.columns: + if rank_method == 'hits' and is_rpm_normalize: + if is_positive(df, motif_name, threshold_mean, threshold_std, threshold_median, min_max_difference): + positive_motifs.append(motif_name) + motifs_value.append('positive') + else: + motifs_value.append('negative') + else: + if is_positive(df, motif_name, threshold_mean, threshold_std, threshold_median, min_max_difference): + positive_motifs.append(motif_name) + motifs_value.append('positive') + else: + motifs_value.append('negative') + df.loc['values'] = motifs_value + threshold = [threshold_mean, threshold_std, threshold_median, None, None, + threshold_mean, threshold_std, threshold_median, None, None, None] + df.insert(0, 'threshold', threshold) + return df,positive_motifs + + +def statistical_calculation(df, output_path, done_path, is_rpm_normalize, invalid_mix, threshold_mean, threshold_std, threshold_median, + min_max_difference, rank_method, normalize_factor, normalize_method_hits, normalize_section, + fixed_min, fixed_max, argv): + source_df = df.copy() + labels = list(set(df['label'])) + biological_condition = labels[1] if labels[0]=='other' else labels[0] + if invalid_mix: + artifuct_motifs = is_artifact(df, biological_condition, invalid_mix) + df = df.drop(artifuct_motifs, axis=1) + source_df = df.copy() + df = df.drop('sample_name', axis=1) + df.set_index('label', inplace=True) + if len(list(df.columns)) == 0: + logger.info('All motifs removed by invalid mix section') + return + if rank_method == 'pval': + df = 1-df + if not is_rpm_normalize and (normalize_factor == 'log' or rank_method =='hits'): + df = normalize(df, normalize_factor, normalize_method_hits, normalize_section, rank_method, fixed_min, fixed_max) + df_BC = df.loc[biological_condition] + df_other = df.loc['other'] + # Calculate the statistical functions - mean, std, median + df_BC_statistical = calculation(df_BC, 'BC') + df_other_statistical = calculation(df_other, 'other') + # Concat two dataframe to one + df_statistical = pd.concat([df_BC_statistical, df_other_statistical]) + # Left only the positive motifs + df_statistical, positive_motifs = find_positive_motifs(df_statistical, threshold_mean, threshold_std, threshold_median, min_max_difference, rank_method, is_rpm_normalize) + # Write the results + write_results(source_df, df_statistical, positive_motifs, output_path) + + with open(done_path, 'w') as f: + f.write(' '.join(argv) + '\n') + +if __name__ == '__main__': + + print(f'Starting {sys.argv[0]}. Executed command is:\n{" ".join(sys.argv)}') + + import argparse + + parser = argparse.ArgumentParser() + parser.add_argument('data_path', type=str, help='A csv file with data matrix to model') + parser.add_argument('output_path', type=str, help='Path to base name file for output the results') + parser.add_argument('done_file_path', type=str, help='A path to a file that signals that the script finished running successfully.') + parser.add_argument('--is_rpm_normalize', action='store_true', help='The data is already normalize by rpm') + parser.add_argument('--invalid_mix',type=str, default=None, help='Sample name considered negative. e.g. "native') + parser.add_argument('--threshold_mean', default=None, + type=lambda x: float(x) if 0 < float(x) < 1 + else parser.error(f'The threshold of the mean diffrence should be between 0 to 1'), + help='Positive motif threshold of the difference between the mean of the BC and mean of others') + parser.add_argument('--threshold_std', default=None, + type=lambda x: float(x) if 0 < float(x) < 1 + else parser.error(f'The threshold of the std diffrence should be between 0 to 1'), + help='Positive motif threshold of the difference between the std of the BC and std of others') + parser.add_argument('--threshold_median', default=None, + type=lambda x: float(x) if 0 < float(x) < 1 + else parser.error(f'The threshold of the median diffrence should be between 0 to 1'), + help='Positive motif threshold of the difference between the median of the BC and median of others') + parser.add_argument('--min_max_difference', action='store_true', help='Positive motif if the minmal value of bc is bigger than the maximal value of other') + parser.add_argument('--rank_method', choices=['pval', 'tfidf', 'shuffles', 'hits'], default='hits', help='Motifs ranking method') + parser.add_argument('--normalize_factor', choices=['linear', 'log'], default='linear', help='type of factor to normalize the data. \ + If nonlinear data (values are not in the same distance) use log ,otherwise use linear') + parser.add_argument('--normalize_method_hits', choices=['min_max', 'max', 'fixed_min_max'], default='min_max', + help='Method affects which values the normalization will change the data. e.g. min max bring all values into the range [0,1]') + parser.add_argument('--normalize_section', choices=['per_motif','per_exp'], default='per_motif', help='Normalize the data by calculate the min and max per motif or over all the exp data') + parser.add_argument('--fixed_min', type=int, default=None, help='In case of fixed_min_max for normalize_method_hits set the minimum value') + parser.add_argument('--fixed_max', type=int, default=None, help='In case of fixed_min_max for normalize_method_hits set the maximum value') + parser.add_argument('-v', '--verbose', action='store_true', help='Increase output verbosity') + args = parser.parse_args() + + if args.verbose: + logging.basicConfig(level=logging.DEBUG) + else: + logging.basicConfig(level=logging.INFO) + logger = logging.getLogger('main') + + df = pd.read_csv(args.data_path) + statistical_calculation(df, args.output_path, args.done_file_path, args.is_rpm_normalize, args.invalid_mix, + args.threshold_mean, args.threshold_std, args.threshold_median, args.min_max_difference, + args.rank_method, args.normalize_factor, args.normalize_method_hits, args.normalize_section, + args.fixed_min, args.fixed_max, sys.argv) + \ No newline at end of file diff --git a/model_fitting/random_forest.py b/model_fitting/random_forest.py index 3421706..f4b46f7 100644 --- a/model_fitting/random_forest.py +++ b/model_fitting/random_forest.py @@ -1,175 +1,306 @@ -import sys -import os -import seaborn as sns -import numpy as np -import pandas as pd -import matplotlib.pyplot as plt -import joblib -from sklearn.ensemble import RandomForestClassifier -from sklearn.model_selection import cross_val_score - -import logging -logging.basicConfig(level=logging.INFO) -logger = logging.getLogger('main') - - - -def parse_data(file_path): - # reading the CSV file if it's legal - try: - data = pd.read_csv(file_path, engine='python') - except Exception as e: - exit(f'Cannot analyse data! {e}') - - # separating train and test samples (fifth sample of each mAb) - train_data = data[~data['sample_name'].str.contains('test')] - test_data = data[data['sample_name'].str.contains('test')] - - # set sample names as index - train_data.set_index('sample_name', inplace=True) - test_data.set_index('sample_name', inplace=True) - - return train_data, test_data - - -def plot_heat_map(df, number_of_features, output_path, hits, number_of_samples): - #plt.figure(dpi=3000) - cm = sns.clustermap(df, cmap="Blues", col_cluster=False, yticklabels=True) - plt.setp(cm.ax_heatmap.yaxis.get_majorticklabels(), fontsize=150/number_of_samples) - cm.ax_heatmap.set_title(f"A heat-map of the significance of the top {number_of_features} discriminatory motifs") - cm.savefig(f"{output_path}/{number_of_features}.svg", format='svg', bbox_inches="tight") - plt.close() - - -def plot_error_rate(errors, features, output_path_dir): - plt.figure(dpi=1000) - plt.plot(features, errors, '--o') - plt.xscale('log') - plt.ylim(-0.02, 1) - plt.xlabel("features") - plt.ylabel("error rate") - plt.savefig(f"{output_path_dir}/error_rate.png") - plt.close() - - -def train_models(csv_file_path, done_path, num_of_iterations, argv): - logging.info('Parsing data...') - train_data, test_data = parse_data(csv_file_path) - y = np.array(train_data['label']) # saving the (true) labels - train_data.drop(['label'], axis=1, inplace=True) - test_data.drop(['label'], axis=1, inplace=True) - X = np.array(train_data) # saving an array of the variables - is_hits_data = 'hits' in csv_file_path - - logging.info('Preparing output path...') - csv_folder, csv_file_name = os.path.split(csv_file_path) - csv_file_prefix = os.path.splitext(csv_file_name)[0] # without extension - output_path = os.path.join(csv_folder, f'{csv_file_prefix}_model') - feature_selection_summary_path = f'{output_path}/feature_selection_summary.txt' - for i in range(num_of_iterations): - output_path_i = os.path.join(output_path, str(i)) - if not os.path.exists(output_path_i): - logging.info('Creating output path...') - os.makedirs(output_path_i) - - errors, features = train(X, y, is_hits_data, train_data, output_path_i) - - plot_error_rate(errors, features, output_path_i) - - with open(feature_selection_summary_path, 'w' if i == 0 else 'a') as f: # override only on first iteration - f.write(f'{i}\t{features[-1]}\n') - - with open(done_path, 'w') as f: - f.write(' '.join(argv) + '\n') - - -def train(X, y, hits_data, train_data, output_path): - logging.info('Training...') - rf = RandomForestClassifier(n_estimators=1000) # number of trees - model = rf.fit(X, y) - importance = model.feature_importances_ - indexes = np.argsort(importance)[::-1] # decreasing order of importance - train_data = train_data.iloc[:, indexes] # sort features by their importance - - with open(f'{output_path}/feature_importance.txt', 'w') as f: - importance=importance[indexes] - features = train_data.columns.tolist() - for i in range(len(importance)): - f.write(f'{features[i]}\t{importance[i]}\n') - - # transform the data for better contrast in the visualization - if hits_data: # hits data - train_data = np.log2(train_data+1) # pseudo counts - # df = df - else: # p-values data - train_data = -np.log2(train_data) - - number_of_samples, number_of_features = X.shape - error_rate = previous_error_rate = 1 - error_rates = [] - number_of_features_per_model = [] - while error_rate <= previous_error_rate and number_of_features >= 1: - logger.info(f'Number of features is {number_of_features}') - number_of_features_per_model.append(number_of_features) - - # save previous error_rate to make sure the performances do not deteriorate - previous_error_rate = error_rate - - # compute current model accuracy for each fold of the cross validation - cv_score = cross_val_score(rf, X, y, cv=3, n_jobs=-1) - - # current model error_rate rate - error_rate = 1 - cv_score.mean() - error_rates.append(error_rate) - logger.info(f'Error rate is {error_rate}') - - # save current model features to a csv file - df = train_data.iloc[:, :number_of_features] - if error_rate <= previous_error_rate: - df.to_csv(f"{output_path}/Top_{number_of_features}_features.csv") - - plot_heat_map(df, number_of_features, output_path, hits_data, number_of_samples) - - # save the model itself (serialized) for future use - joblib.dump(model, - os.path.join(output_path, f'Top_{number_of_features}_features_model.pkl')) - - # update number of features - number_of_features //= 2 - if number_of_features < 1: - continue - - # extract only the (new) half most important features - X = np.array(train_data.iloc[:, :number_of_features]) - - # re-evaluate - rf = RandomForestClassifier(n_estimators=100) - model = rf.fit(X, y) - - # Sanity check for debugging: predicting the test data - # change the logging level (line 12) to logging.DEBUG to get the predictions - # logger.debug(model.predict(test_data.iloc[:, indexes[:number_of_features]])) - return error_rates, number_of_features_per_model - - -if __name__ == '__main__': - - print(f'Starting {sys.argv[0]}. Executed command is:\n{" ".join(sys.argv)}') - - import argparse - - parser = argparse.ArgumentParser() - parser.add_argument('data_path', type=str, help='A csv file with data matrix to model ') - parser.add_argument('done_file_path', help='A path to a file that signals that the script finished running successfully.') - parser.add_argument('--num_of_iterations', default=10, help='How many should the RF run?') - parser.add_argument('-v', '--verbose', action='store_true', help='Increase output verbosity') - args = parser.parse_args() - - if args.verbose: - logging.basicConfig(level=logging.DEBUG) - else: - logging.basicConfig(level=logging.INFO) - logger = logging.getLogger('main') - - train_models(args.data_path, args.done_file_path, args.num_of_iterations, argv=sys.argv) - +import time +import sys +import os +import shutil +import numpy as np +import pandas as pd +import joblib +from sklearn.ensemble import RandomForestClassifier +from sklearn.model_selection import cross_val_score, StratifiedKFold + +import logging +logging.basicConfig(level=logging.INFO) +logger = logging.getLogger('main') + +if os.path.exists('/groups/pupko/orenavr2/'): + src_dir = '/groups/pupko/orenavr2/igomeProfilingPipeline/src' +elif os.path.exists('/Users/Oren/Dropbox/Projects/'): + src_dir = '/Users/Oren/Dropbox/Projects/gershoni/src' +else: + src_dir = '.' +sys.path.insert(0, src_dir) + +from auxiliaries.pipeline_auxiliaries import submit_pipeline_step, wait_for_results, log_scale, generate_heat_map + + +def parse_data(file_path): + try: + data = pd.read_csv(file_path, engine='python') + except Exception as e: + exit(f'Cannot analyse data! {e}') + + # create masks for separating train and test samples (fifth sample of each mAb) + test_rows_mask = data['sample_name'].str.contains('test') + train_rows_mask = ~test_rows_mask + + # separating train and test samples (fifth sample of each mAb) + # a matrix of the actual feature values + y_train = data['label'][train_rows_mask] + y_test = data['label'][test_rows_mask] + + sample_names_train = data['sample_name'][train_rows_mask] + sample_names_test = data['sample_name'][test_rows_mask] + + # don't need the labels and sample names anymore + data.drop(['sample_name', 'label'], axis=1, inplace=True) + # a matrix of the actual feature values + X_train = data[train_rows_mask].values + X_test = data[test_rows_mask].values + + feature_names = np.array(data.columns) + + return X_train, y_train, X_test, y_test, feature_names, sample_names_train, sample_names_test + + +def get_hyperparameters_grid(seed): + # Number of trees in random forest + n_estimators = [int(x) for x in np.linspace(start=100, stop=2000, num=20)] + # Number of features to consider at every split + max_features = ['auto', 'sqrt'] + # Maximum number of levels in tree + max_depth = [int(x) for x in np.linspace(10, 110, num=11)] + max_depth.append(None) + # Minimum number of samples required to split a node + min_samples_split = [2, 5, 10] + # Minimum number of samples required at each leaf node + min_samples_leaf = [1, 2, 4, 8] + # Method of selecting samples for training each tree + bootstrap = [True, False] + # Create the random grid + random_grid = {'n_estimators': n_estimators, + 'max_features': max_features, + 'max_depth': max_depth, + 'min_samples_split': min_samples_split, + 'min_samples_leaf': min_samples_leaf, + 'bootstrap': bootstrap} + + if os.path.exists('/Users/Oren'): + # use all cores when running locally. + # does not apply on the cluster (needed to be set as well in the .pbs file) + random_grid['n_jobs'] = [-1] + + # Use the random grid to search for best hyperparameters + return random_grid + + +def sample_configurations(hyperparameters_grid, num_of_configurations_to_sample, seed): + configurations = [] + for i in range(num_of_configurations_to_sample): + configuration = {} + for key in hyperparameters_grid: + np.random.seed(seed+i) + configuration[key] = np.random.choice(hyperparameters_grid[key], size=1)[0] + configurations.append(configuration) + return configurations + + +def save_model_features(X, feature_indexes, feature_names, sample_names, output_path): + df = pd.DataFrame() + for i in range(len(feature_names)): + df[feature_names[i]] = X[:, feature_indexes[i]] # train_data.iloc[:, :number_of_features] + df.set_index(sample_names, inplace=True) + df.to_csv(f"{output_path}.csv") + return df + + + +def write_results_feature_selection_summary(feature_selection_summary_path, path_dir): + models = sorted([x[0] for x in os.walk(path_dir)]) + del models[0] + feature_selection_summary_f = open(feature_selection_summary_path, 'a') + for path_number_model in models: + path_file = f'{path_number_model}/feature_selection.txt' + if os.path.exists(path_file): + with open(path_file) as infile: + line = infile.readline() + feature_selection_summary_f.write(line) + os.remove(path_file) + feature_selection_summary_f.flush() + feature_selection_summary_f.close() + # add 200 millisecond delay before return from function: + time.sleep(0.2) + +def train_models(csv_file_path, done_path, logs_dir,error_path, num_of_configurations_to_sample, number_parallel_random_forest, min_value_error, + rank_method, cv_num_of_splits, seed, random_forest_seed, queue_name, verbose, argv): + logging.info('Preparing output path...') + csv_folder, csv_file_name = os.path.split(csv_file_path) + csv_file_prefix = os.path.splitext(csv_file_name)[0] # without extension + output_path = os.path.join(csv_folder, f'{csv_file_prefix}_model') + os.makedirs(output_path, exist_ok=True) + + best_model_path = os.path.join(output_path, f'best_model') + + feature_selection_summary_path = f'{output_path}/feature_selection_summary.txt' + + logging.info('Parsing data...') + + X_train, y_train, X_test, y_test, feature_names, sample_names_train, sample_names_test = parse_data(csv_file_path) + + # single feature analysis + logging.info('Applying single feature analysis...') + perfect_feature_names, perfect_feature_indexes = measure_each_feature_accuracy(X_train, y_train, feature_names, output_path, seed, cv_num_of_splits) + if perfect_feature_names: + df = save_model_features(X_train, perfect_feature_indexes, perfect_feature_names, sample_names_train, f'{output_path}/perfect_feature_names') + generate_heat_map(df, df.shape[1], rank_method, df.shape[0], f'{output_path}/perfect_feature_names') + else: + # touch a file so we can see that there were no perfect features + with open(f'{output_path}/perfect_feature_names', 'w') as f: + pass + + # feature selection analysis + logging.info('\nApplying feature selection analysis...') + if cv_num_of_splits < 2: + logging.info('Number of CV folds is less than 2. ' + 'Updating number of splits to number of samples and applying Leave One Out approach!') + cv_num_of_splits = len(y_train) + logging.info(f'Number of CV folds is {cv_num_of_splits}.') + + logger.info('\n'+'#'*100 + f'\nTrue labels:\n{y_train.tolist()}\n' + '#'*100 + '\n') + + logging.info('Sampling hyperparameters...') + hyperparameters_grid = get_hyperparameters_grid(seed) + + feature_selection_summary_f = open(feature_selection_summary_path, 'w') + feature_selection_summary_f.write(f'model_number\tnum_of_features\tfinal_error_rate\n') + feature_selection_summary_f.close() + + sampled_configurations = sample_configurations(hyperparameters_grid, num_of_configurations_to_sample, seed) + script_name = 'model_fitting/train_random_forest.py' + num_of_expected_results = 0 + all_cmds_params = [] + for i, configuration in enumerate(sampled_configurations): + model_number = str(i).zfill(len(str(num_of_configurations_to_sample))) + output_path_i = os.path.join(output_path, model_number) + logging.info(f'Creating output path #{i}...') + os.makedirs(output_path_i, exist_ok=True) + file_save_configuration = save_configuration_to_txt_file(configuration, output_path_i) + logging.info(f'Configuration #{i} hyper-parameters are:\n{configuration}') + done_file_path=os.path.join(logs_dir, f'{model_number}_{csv_file_prefix}_done_train_random_forest.txt') + cmds=[file_save_configuration, csv_file_path, rank_method, output_path_i, + model_number, done_file_path, '--random_forest_seed', random_forest_seed, + '--cv_num_of_splits', cv_num_of_splits] + if not os.path.exists(done_file_path): + all_cmds_params.append(cmds) + else: + logger.debug(f'Skipping random forest train as {done_file_path} found') + num_of_expected_results +=1 + + executable = 'python' + if len(all_cmds_params) > 0: + stop = False + for count, cmds_params in enumerate(all_cmds_params): + model_number = cmds_params[4] + cmd = submit_pipeline_step(script_name, [cmds_params], + logs_dir, f'{csv_file_prefix}_{model_number}_train_random_forest', + queue_name, verbose, executable=executable) + num_of_expected_results += 1 # a single job for each train random forest + if (count + 1) % number_parallel_random_forest == 0 or (count + 1) == num_of_configurations_to_sample: + wait_for_results(script_name, logs_dir, num_of_expected_results, example_cmd=cmd, + error_file_path = error_path, suffix = f'_{csv_file_prefix}_done_train_random_forest.txt') + #write the results to feature_selection_summary file + write_results_feature_selection_summary(feature_selection_summary_path, output_path) + #check if we found the best model and can stop run + models_stats = pd.read_csv(feature_selection_summary_path, sep='\t', dtype={'model_number': str, 'num_of_features':int, 'final_error_rate': float }) + for feature, error in zip(models_stats['num_of_features'], models_stats['final_error_rate']): + if feature == 1 and error <= min_value_error: + stop = True + break + if stop: + break + else: + logger.info(f'Skipping random forest train, all found') + + feature_selection_summary_f.close() + + # find who was the best performing model + models_stats = pd.read_csv(feature_selection_summary_path, sep='\t', dtype={'model_number': str, 'num_of_features':int, 'final_error_rate': float }) + lowest_error_models = models_stats[models_stats['final_error_rate'] == + min(models_stats['final_error_rate'])] + + # in case there is a tie between best models, we choose the model with the lowest number of features + if len(lowest_error_models) > 1: + lowest_error_models = lowest_error_models[lowest_error_models['num_of_features'] == + min(lowest_error_models['num_of_features'])] + + best_model = lowest_error_models['model_number'].iloc[0] + + # keep only best model (and remove the rest) + shutil.copytree(f'{output_path}/{best_model}', best_model_path) + for folder in os.listdir(output_path): + path = f'{output_path}/{folder}' + if folder == 'best_model' or not os.path.isdir(path): + continue + shutil.rmtree(path) + + with open(done_path, 'w') as f: + f.write(' '.join(argv) + '\n') + + +def measure_each_feature_accuracy(X_train, y_train, feature_names, output_path, seed, cv_num_of_splits): + + feature_to_avg_accuracy = {} + rf = RandomForestClassifier(random_state=np.random.seed(seed)) + + for i, feature in enumerate(feature_names): + logger.info(f'Checking feature {feature} number {i}') + cv_score = cross_val_score(rf, X_train[:, i].reshape(-1, 1), y_train, cv=StratifiedKFold(cv_num_of_splits, shuffle=True)).mean() + if cv_score == 1: + logger.info('-' * 10 + f'{feature} has 100% accuracy!' + '-' * 10) + feature_to_avg_accuracy[feature] = cv_score + + with open(f'{output_path}/single_feature_accuracy.txt', 'w') as f: + f.write('Feature\tAccuracy_on_cv\n') + for feature in sorted(feature_to_avg_accuracy, key=feature_to_avg_accuracy.get, reverse=True): + f.write(f'{feature}\t{feature_to_avg_accuracy[feature]}\n') + + perfect_feature_names = [] + perfect_feature_indexes = [] + with open(f'{output_path}/features_with_perfect_accuracy.txt', 'w') as f: + for i, feature in enumerate(feature_to_avg_accuracy): + if feature_to_avg_accuracy[feature] == 1: + perfect_feature_names.append(feature) + perfect_feature_indexes.append(i) + f.write(f'{feature}\n') + + return perfect_feature_names, perfect_feature_indexes + + +def save_configuration_to_txt_file(sampled_configuration, output_path_i): + file_save_configuration=f'{output_path_i}/hyperparameters_configuration.txt' + with open(f'{output_path_i}/hyperparameters_configuration.txt', 'w') as f: + for key in sampled_configuration: + f.write(f'{key}={sampled_configuration[key]}\n') + joblib.dump(sampled_configuration, f'{output_path_i}/hyperparameters_configuration.pkl') + return file_save_configuration + +if __name__ == '__main__': + + print(f'Starting {sys.argv[0]}. Executed command is:\n{" ".join(sys.argv)}') + + import argparse + + parser = argparse.ArgumentParser() + parser.add_argument('data_path', type=str, help='A csv file with data matrix to model ') + parser.add_argument('done_file_path', help='A path to a file that signals that the script finished running successfully.') + parser.add_argument('logs_dir', help='A path for the log dir') + parser.add_argument('error_path', help='Path for error file') + + parser.add_argument('--num_of_configurations_to_sample', default=100, type=int, help='How many random configurations of hyperparameters should be sampled?') + parser.add_argument('--number_parallel_random_forest', default=20, type=int, help='How many random forest configurations to run in parallel') + parser.add_argument('--min_value_error_random_forest', default=0, type=float, help='A random forest model error value for convergence allowing to stop early') + parser.add_argument('--cv_num_of_splits', type=int ,default=2, help='How folds should be in the cross validation process? (use 0 for leave one out)') + parser.add_argument('--seed', type=int, default=42, help='Seed number for reconstructing experiments') + parser.add_argument('--random_forest_seed', default=123 , type=int, help='Random seed value for generating random forest configurations') + parser.add_argument('--rank_method', choices=['pval', 'tfidf', 'shuffles', 'hits'], default='hits', help='Motifs ranking method') + parser.add_argument('-q', '--queue', default='pupkoweb', type=str, help='a queue to which the jobs will be submitted') + parser.add_argument('-v', '--verbose', action='store_true', help='Increase output verbosity') + args = parser.parse_args() + import logging + if args.verbose: + logging.basicConfig(level=logging.DEBUG) + else: + logging.basicConfig(level=logging.INFO) + logger = logging.getLogger('main') + + train_models(args.data_path, args.done_file_path, args.logs_dir, args.error_path, args.num_of_configurations_to_sample, args.number_parallel_random_forest, + args.min_value_error_random_forest, args.rank_method, args.cv_num_of_splits, args.seed, args.random_forest_seed, args.queue ,args.verbose, argv=sys.argv) diff --git a/model_fitting/scan_peptides_vs_motifs.py b/model_fitting/scan_peptides_vs_motifs.py index 98f7a77..8298403 100644 --- a/model_fitting/scan_peptides_vs_motifs.py +++ b/model_fitting/scan_peptides_vs_motifs.py @@ -5,22 +5,48 @@ import sys if os.path.exists('/groups/pupko/orenavr2/'): src_dir = '/groups/pupko/orenavr2/igomeProfilingPipeline/src' -else: +elif os.path.exists('/Users/Oren/Dropbox/Projects/'): src_dir = '/Users/Oren/Dropbox/Projects/gershoni/src' +else: + src_dir = '.' sys.path.insert(0, src_dir) from auxiliaries.pipeline_auxiliaries import verify_file_is_not_empty +from time import time -def calculate_pssm_thresholds(meme_path, cutoffs_path, faa_path, number_of_random_pssms, output_path, done_path, - argv='no_argv', - pssm_score_peptide='/groups/pupko/orenavr2/igomeProfilingPipeline/src/PSSM_score_Peptide/PSSM_score_Peptide'): +def calculate_pssm_thresholds(meme_path, cutoffs_path, faa_path, number_of_random_pssms, + shuffles, shuffles_percent, shuffles_digits, no_rpm_factor, output_path, done_path, + rank_method, no_use_rpm_faa_scanning, sequence_hit_motif_path, no_output_sequences_scanning, + argv='no_argv', pssm_score_peptide='./PSSM_score_Peptide/PSSM_score_Peptide'): if not os.path.exists(output_path): # TODO: any modules to load? - cmd = f'{pssm_score_peptide} -pssm {meme_path} -pssm_cutoffs {cutoffs_path} -seq {faa_path} ' \ - f'-out {output_path} -NrandPSSM {number_of_random_pssms} -CalcPSSM_Pval' - logger.info(f'{datetime.datetime.now()}: starting CalcPSSM_Pval. Executed command is:\n{cmd}') + if rank_method == 'pval': + cmd = f'{pssm_score_peptide} -pssm {meme_path} -pssm_cutoffs {cutoffs_path} -seq {faa_path} ' \ + f'-out {output_path} -NrandPSSM {number_of_random_pssms} -CalcPSSM_Pval' + if not no_rpm_factor: + cmd += ' -useFactor' + if not no_output_sequences_scanning and sequence_hit_motif_path: + cmd += f' -outputSequences -sequenceHitMotifPath {sequence_hit_motif_path}' + if not no_use_rpm_faa_scanning: + cmd += ' -useRpmFaaScanning -useFactor' + logger.info(f'{datetime.datetime.now()}: starting CalcPSSM_Pval. Executed command is:\n{cmd}') + elif rank_method == 'tfidf': + cmd = f'./hits_cpp/hits -m {meme_path} -c {cutoffs_path} -s {faa_path} -o {output_path} --outputSequences' + logger.info(f'{datetime.datetime.now()}: starting TF-IDF\' hits. Executed command is:\n{cmd}') + else: # shuffles + cmd = f'./hits_cpp/hits -m {meme_path} -c {cutoffs_path} -s {faa_path} -o {output_path} --shuffles {shuffles} '\ + f'--shufflesPercent {shuffles_percent} --shufflesDigits {shuffles_digits}' + if not no_rpm_factor: + cmd += ' --useFactor' + if not no_output_sequences_scanning and sequence_hit_motif_path: + cmd += f' --outputSequences --sequenceHitMotifPath {sequence_hit_motif_path}' + if not no_use_rpm_faa_scanning: + cmd += ' --useRpmFaaScanning --useFactor' + + logger.info(f'{datetime.datetime.now()}: starting Shuffles\' hits. Executed command is:\n{cmd}') + subprocess.run(cmd, shell=True) else: logger.info(f'{datetime.datetime.now()}: skipping scanning calculation as it is already exist at:\n{output_path}') @@ -42,9 +68,17 @@ def calculate_pssm_thresholds(meme_path, cutoffs_path, faa_path, number_of_rando parser.add_argument('meme_file_path', help='A path to a meme file with motifs against which a set of random peptides will be scanned') parser.add_argument('cutoffs_file_path', help='A path to a cutoffs file (peptide above cutoff? -> peptide is part of the motif') parser.add_argument('faa_file_path', help='A path to a faa file with peptides to scan against the pssms in the meme file') + parser.add_argument('rank_method', choices=['pval', 'tfidf', 'shuffles'], default='pval', help='Motifs ranking method') parser.add_argument('number_of_random_pssms', type=int, help='Number of pssm permutations') parser.add_argument('output_path', help='A path to which the Pvalues will be written to') - parser.add_argument('done_file_path', help='A path to a file that signals that the script finished running successfully.') + parser.add_argument('done_file_path', help='A path to a file that signals that the script finished running successfully') + parser.add_argument('--shuffles', default=10, type=int, help='Number of controlled shuffles permutations') + parser.add_argument('--shuffles_percent', default=0.2, type=float, help='Percent from shuffle with greatest number of hits (0-1)') + parser.add_argument('--shuffles_digits', default=2, type=int, help='Number of digits after the point to print in scanning files') + parser.add_argument('--no_rpm_factor', action='store_true', help='Disable multiplication hits by factor rpm for normalization') + parser.add_argument('--sequence_hit_motif_path', type=str, help='A path for file to write the sequences that had hits with motif') + parser.add_argument('--no_output_sequences_scanning', action='store_true', help='Disable storing the output sequences that had hits') + parser.add_argument('--no_use_rpm_faa_scanning', action='store_true', help='Disable performance of scanning script with unique rpm faa file') parser.add_argument('-v', '--verbose', action='store_true', help='Increase output verbosity') args = parser.parse_args() @@ -54,6 +88,10 @@ def calculate_pssm_thresholds(meme_path, cutoffs_path, faa_path, number_of_rando logging.basicConfig(level=logging.INFO) logger = logging.getLogger('main') + start = time() calculate_pssm_thresholds(args.meme_file_path, args.cutoffs_file_path, args.faa_file_path, - args.number_of_random_pssms, args.output_path, args.done_file_path, argv=sys.argv) - + args.number_of_random_pssms, args.shuffles, args.shuffles_percent, args.shuffles_digits, args.no_rpm_factor, + args.output_path, args.done_file_path, args.rank_method, args.no_use_rpm_faa_scanning, + args.sequence_hit_motif_path, args.no_output_sequences_scanning, argv=sys.argv) + end = time() + print(f'total time (sec): {end - start}') diff --git a/model_fitting/train_random_forest.py b/model_fitting/train_random_forest.py new file mode 100644 index 0000000..e821dd3 --- /dev/null +++ b/model_fitting/train_random_forest.py @@ -0,0 +1,217 @@ +import sys +import joblib +import os +import matplotlib.pyplot as plt +from sklearn.ensemble import RandomForestClassifier +from sklearn.model_selection import cross_val_score, StratifiedKFold +from sklearn.metrics import plot_roc_curve +import pandas as pd +import numpy as np +if os.path.exists('/groups/pupko/orenavr2/'): + src_dir = '/groups/pupko/orenavr2/igomeProfilingPipeline/src' +elif os.path.exists('/Users/Oren/Dropbox/Projects/'): + src_dir = '/Users/Oren/Dropbox/Projects/gershoni/src' +else: + src_dir = '.' +sys.path.insert(0, src_dir) +from auxiliaries.pipeline_auxiliaries import generate_heat_map + + +def parse_data(file_path): + try: + data = pd.read_csv(file_path, engine='python') + except Exception as e: + exit(f'Cannot analyse data! {e}') + + # create masks for separating train and test samples (fifth sample of each mAb) + test_rows_mask = data['sample_name'].str.contains('test') + train_rows_mask = ~test_rows_mask + + # separating train and test samples (fifth sample of each mAb) + # a matrix of the actual feature values + y_train = data['label'][train_rows_mask] + y_test = data['label'][test_rows_mask] + + sample_names_train = data['sample_name'][train_rows_mask] + sample_names_test = data['sample_name'][test_rows_mask] + + # don't need the labels and sample names anymore + data.drop(['sample_name', 'label'], axis=1, inplace=True) + # a matrix of the actual feature values + X_train = data[train_rows_mask].values + X_test = data[test_rows_mask].values + + feature_names = np.array(data.columns) + + return X_train, y_train, X_test, y_test, feature_names, sample_names_train, sample_names_test + + +def generate_roc_curve(X, y, classifier, number_of_features, output_path): + ax = plt.gca() + plot_roc_curve(classifier, X, y, **{'marker': 'o'}, ax=ax) + plt.savefig(f"{output_path}/roc_curve_{number_of_features}.png", format='png', bbox_inches="tight") + plt.close() + + +def plot_error_rate(errors, features, cv_num_of_splits, output_path_dir): + raw_data_path = f"{output_path_dir}/error_rate.txt" + with open(raw_data_path, 'w') as f: + f.write('Features\tErrors\n') + f.write('\n'.join(f'{feature}\t{error}' for feature, error in zip(features[::-1], errors[::-1]))) + plt.figure(dpi=1000) + plt.plot(features, errors, '--o') + plt.xscale('log') + plt.ylim(-0.02, 1) + plt.xlabel('# of Features') + plt.ylabel(f'{cv_num_of_splits}Fold CV Avg. Error Rate') + plot_path = raw_data_path.replace('.txt', '.png') + plt.savefig(plot_path) + plt.close() + + +def train(rf, X, y, feature_names, sample_names, rank_method, output_path, cv_num_of_splits): + + original_feature_names = feature_names[:] + original_X = X[:] + logger.debug('\n'+'#'*100 + f'\nTrue labels:\n{y.tolist()}\n' + '#'*100 + '\n') + + # Fit the best model configuration on the WHOLE dataset + logging.info('Training...') + model = rf.fit(X, y) + importance = model.feature_importances_ + + # the permutation needed to get the feature importances in a decreasing order + decreasing_feature_importance = np.argsort(importance)[::-1] + assert (sorted(importance, reverse=True) == importance[decreasing_feature_importance]).all() + + # the indexes that will be used in the next fitting process + # At first, we start with all features. Next we will remove less important once. + features_indexes_to_keep = range(len(feature_names)) + + # write feature importance to storage + with open(f'{output_path}/feature_importance.txt', 'w') as f: + for i in range(len(importance)): + f.write(f'{feature_names[i]}\t{importance[i]}\n') + + # write sorted feature importance to storage + with open(f'{output_path}/sorted_feature_importance.txt', 'w') as f: + for i in range(len(importance)): + f.write(f'{feature_names[decreasing_feature_importance[i]]}\t' + f'{importance[decreasing_feature_importance[i]]}\n') + + number_of_samples, number_of_features = X.shape + cv_avg_error_rate = previous_cv_avg_error_rate = 1 + cv_avg_error_rates = [] + number_of_features_per_model = [] + while cv_avg_error_rate <= previous_cv_avg_error_rate and number_of_features >= 1: + + # save previous cv_avg_error_rate to make sure the performances do not deteriorate + previous_cv_avg_error_rate = cv_avg_error_rate + + # compute current model accuracy for each fold of the cross validation + cv_score = cross_val_score(model, X, y, cv=StratifiedKFold(cv_num_of_splits)) + + # current model cv_avg_error_rate rate + cv_avg_error_rate = 1 - cv_score.mean() + number_of_features_per_model.append(number_of_features) + cv_avg_error_rates.append(cv_avg_error_rate) + + logger.info(f'Number of features is {number_of_features} with avg. error rate of {cv_avg_error_rate}') + if cv_avg_error_rate > previous_cv_avg_error_rate: + # Stop procedure and discard current stats + break + + # save current model (unsorted) features to a csv file + df = save_model_features(original_X, features_indexes_to_keep, feature_names, sample_names, f'{output_path}/Top_{number_of_features}_features') + + generate_heat_map(df, number_of_features, rank_method, number_of_samples, f'{output_path}/{number_of_features}') + + generate_roc_curve(X, y, rf, number_of_features, output_path) + + # save the model itself (serialized) for future use + joblib.dump(model, os.path.join(output_path, f'Top_{number_of_features}_features_model.pkl')) + + # Sanity check for debugging: predicting the test data + model_score = model.score(X, y) + if 1-model_score > cv_avg_error_rate: + predictions = model.predict(X).tolist() + logging.error('1-model_score > cv_avg_error_rate !!!') + logger.info(f'Full model error rate is {1-model_score}') + logger.info(f'Current model\'s predictions\n{predictions}') + logger.info(f'number_of_features {number_of_features}') + logger.info(f'output_path {output_path}') + + # update number of features + number_of_features //= 2 + + # extract only the (new) half most important features + features_indexes_to_keep = sorted(decreasing_feature_importance[:number_of_features]) + feature_names = original_feature_names[features_indexes_to_keep] + X = original_X[:, features_indexes_to_keep] + + if number_of_features > 0: + # re-evaluate + model = rf.fit(X, y) + + return cv_avg_error_rates, number_of_features_per_model +def save_model_features(X, feature_indexes, feature_names, sample_names, output_path): + df = pd.DataFrame() + for i in range(len(feature_names)): + df[feature_names[i]] = X[:, feature_indexes[i]] # train_data.iloc[:, :number_of_features] + df.set_index(sample_names, inplace=True) + df.to_csv(f"{output_path}.csv") + return df + +def configuration_from_txt_to_dictionary(configuration_path): + configuration = {} + with open(configuration_path, 'r') as f: + for line in f: + (key, val) = line.split('=') + if key == 'max_features' or key == 'bootstrap': + configuration[key] = val.split('\n')[0] + elif (key == 'max_depth' and val == 'None\n'): + configuration[key]=None + else: + configuration[key] = int(val) + return configuration + +def pre_train(configuration_path, csv_file_path, rank_method, output_path_i, model_number, done_file_path, random_forest_seed, cv_num_of_splits, argv): + configuration = configuration_from_txt_to_dictionary(configuration_path) + logger.info(f'Start run random forest for model number {model_number}:') + feature_selection_f = open(f'{output_path_i}/feature_selection.txt', 'w') + rf = RandomForestClassifier(**configuration, random_state=random_forest_seed) + X_train, y_train, X_test, y_test, feature_names, sample_names_train, sample_names_test = parse_data(csv_file_path) + errors, features = train(rf, X_train, y_train, feature_names, sample_names_train, rank_method, output_path_i, cv_num_of_splits) + plot_error_rate(errors, features, cv_num_of_splits, output_path_i) + feature_selection_f.write(f'{model_number}\t{features[-1]}\t{errors[-1]}\n') + feature_selection_f.close() + + with open(done_file_path, 'w') as f: + f.write(' '.join(argv) + '\n') + +if __name__ == '__main__': + print(f'Starting {sys.argv[0]}. Executed command is:\n{" ".join(sys.argv)}') + + import argparse + + parser = argparse.ArgumentParser() + parser.add_argument('configuration_path', type=str, help='A dictionary of the configuration to this model ') + parser.add_argument('csv_file_path', type=str, help='A csv file with data matrix to model') + parser.add_argument('rank_method', type=str, help='if is hits data or pvalue data') + parser.add_argument('output_path_i', type=str, help='A path for the results of this model') + parser.add_argument('model_number', type=str, help='number of model') + parser.add_argument('done_file_path', type=str, help='A path to a file that signals that the script finished running successfully') + parser.add_argument('--random_forest_seed', default=123 , type=int, help='Random seed value for generating random forest configurations') + parser.add_argument('--cv_num_of_splits', default=2, type=int, help='number of CV folds') + parser.add_argument('-v', '--verbose', action='store_true', help='Increase output verbosity') + args = parser.parse_args() + import logging + if args.verbose: + logging.basicConfig(level=logging.DEBUG) + else: + logging.basicConfig(level=logging.INFO) + logger = logging.getLogger('main') + + + pre_train(args.configuration_path, args.csv_file_path, args.rank_method, args.output_path_i, args.model_number, args.done_file_path, + args.random_forest_seed, args.cv_num_of_splits, argv=sys.argv) diff --git a/motif_inference/align_sequences.py b/motif_inference/align_sequences.py index bd24b5d..d5b268e 100644 --- a/motif_inference/align_sequences.py +++ b/motif_inference/align_sequences.py @@ -3,8 +3,10 @@ import sys if os.path.exists('/groups/pupko/orenavr2/'): src_dir = '/groups/pupko/orenavr2/igomeProfilingPipeline/src' -else: +elif os.path.exists('/Users/Oren/Dropbox/Projects/'): src_dir = '/Users/Oren/Dropbox/Projects/gershoni/src' +else: + src_dir = '.' sys.path.insert(0, src_dir) from auxiliaries.pipeline_auxiliaries import verify_file_is_not_empty, remove_redundant_newlines_from_fasta, \ @@ -68,8 +70,3 @@ def reconstruct_msa(sequences_file_path, output_file_path, done_path, argv='no_a logger = logging.getLogger('main') reconstruct_msa(args.sequences_file_path, args.output_file_path, args.done_file_path, sys.argv) - - - - - diff --git a/motif_inference/calculate_pssm_cutoffs.py b/motif_inference/calculate_pssm_cutoffs.py index dfbe92d..1157361 100644 --- a/motif_inference/calculate_pssm_cutoffs.py +++ b/motif_inference/calculate_pssm_cutoffs.py @@ -5,19 +5,22 @@ import sys if os.path.exists('/groups/pupko/orenavr2/'): src_dir = '/groups/pupko/orenavr2/igomeProfilingPipeline/src' -else: +elif os.path.exists('/Users/Oren/Dropbox/Projects/'): src_dir = '/Users/Oren/Dropbox/Projects/gershoni/src' +else: + src_dir = '.' sys.path.insert(0, src_dir) -from auxiliaries.pipeline_auxiliaries import verify_file_is_not_empty, get_cluster_size_from_name +from auxiliaries.pipeline_auxiliaries import verify_file_is_not_empty -def calculate_pssm_cutoffs(meme_path, output_path, done_path, argv='no_argv', - pssm_score_peptide='/groups/pupko/orenavr2/igomeProfilingPipeline/src/PSSM_score_Peptide/PSSM_score_Peptide'): +def calculate_pssm_cutoffs(meme_path, output_path, done_path, total_memes, cutoff_random_peptitdes_percentile, min_library_length_cutoff, max_library_length_cutoff, + argv='no_argv', pssm_score_peptide='./PSSM_score_Peptide/PSSM_score_Peptide'): if not os.path.exists(output_path): # TODO: any modules to load? - cmd = f'{pssm_score_peptide} -pssm {meme_path} -pssm_cutoffs {output_path} -CalcPSSM_Cutoff' + cmd = f'{pssm_score_peptide} -pssm {meme_path} -pssm_cutoffs {output_path} -CalcPSSM_Cutoff -total_memes {total_memes} -cutoff_random_peptitdes_percentile {cutoff_random_peptitdes_percentile} ' \ + f'-min_library_length_cutoff {min_library_length_cutoff} -max_library_length_cutoff {max_library_length_cutoff}' logger.info(f'{datetime.datetime.now()}: starting PSSM_score_Peptide. Executed command is:\n{cmd}') subprocess.run(cmd, shell=True) else: @@ -39,7 +42,11 @@ def calculate_pssm_cutoffs(meme_path, output_path, done_path, argv='no_argv', parser = argparse.ArgumentParser() parser.add_argument('meme_file_path', help='A path to a meme file') parser.add_argument('output_path', help='A path in which a new subfolder with the united motifs will be written to') - parser.add_argument('done_file_path', help='A path to a file that signals that the script finished running successfully.') + parser.add_argument('done_file_path', help='A path to a file that signals that the script finished running successfully') + parser.add_argument('--total_memes', type=int, default=0, help='Total memes in biological condition. Used if input data is splitted') + parser.add_argument('--cutoff_random_peptitdes_percentile', type=float, default=0.05, help='Calculate cutoff (hit threshold) from random peptides\' top percentile score') + parser.add_argument('--min_library_length_cutoff', type=int, default=5, help='Minimal value of libraries to generate random peptitdes') + parser.add_argument('--max_library_length_cutoff', type=int, default=14, help='Maximum value of libraries to generate random peptitdes') parser.add_argument('-v', '--verbose', action='store_true', help='Increase output verbosity') args = parser.parse_args() @@ -49,5 +56,5 @@ def calculate_pssm_cutoffs(meme_path, output_path, done_path, argv='no_argv', logging.basicConfig(level=logging.INFO) logger = logging.getLogger('main') - calculate_pssm_cutoffs(args.meme_file_path, args.output_path, args.done_file_path, argv=sys.argv) - + calculate_pssm_cutoffs(args.meme_file_path, args.output_path, args.done_file_path, args.total_memes, args.cutoff_random_peptitdes_percentile, + args.min_library_length_cutoff, args.max_library_length_cutoff, argv=sys.argv) diff --git a/motif_inference/cluster.py b/motif_inference/cluster.py index c90035d..ddfd8b8 100644 --- a/motif_inference/cluster.py +++ b/motif_inference/cluster.py @@ -3,17 +3,20 @@ import datetime import os import sys +from time import time if os.path.exists('/groups/pupko/orenavr2/'): src_dir = '/groups/pupko/orenavr2/igomeProfilingPipeline/src' -else: +elif os.path.exists('/Users/Oren/Dropbox/Projects/'): src_dir = '/Users/Oren/Dropbox/Projects/gershoni/src' +else: + src_dir = '.' sys.path.insert(0, src_dir) -from auxiliaries.pipeline_auxiliaries import verify_file_is_not_empty +from auxiliaries.pipeline_auxiliaries import verify_file_is_not_empty, measure_time def cluster_sequences(fasta_file, output_prefix, done_file_path, threshold, word_length, - throw_sequences_shorter_than, argv='no_argv'): + throw_sequences_shorter_than, cluster_algorithm_mode, argv='no_argv'): verify_file_is_not_empty(fasta_file) @@ -24,7 +27,8 @@ def cluster_sequences(fasta_file, output_prefix, done_file_path, threshold, word f'-o {output_prefix} ' \ f'-c {threshold} ' \ f'-n {word_length} ' \ - f'-l {throw_sequences_shorter_than}' + f'-l {throw_sequences_shorter_than} ' \ + f'-g {cluster_algorithm_mode}' logger.info(f'Starting CD-hit. Executed command is:\n{cmd}') subprocess.call(cmd, shell=True) @@ -45,12 +49,15 @@ def cluster_sequences(fasta_file, output_prefix, done_file_path, threshold, word parser.add_argument('fasta_file', help='A fasta file to collapse for unique sequences and their counts') parser.add_argument('output_prefix', help='A file prefix in which the 2 result files will use as an output path') parser.add_argument('done_file_path', help='A path to a file that signals that the script finished running successfully.') - parser.add_argument('--threshold', default='0.5', help='Minimal sequence similarity threshold required', + parser.add_argument('sample_name', type=str, help='The name of the sample that cluster on') + + parser.add_argument('--threshold', default='0.6', help='Minimal sequence similarity threshold required', type=lambda x: float(x) if 0.4 <= float(x) <= 1 else parser.error(f'CD-hit allows thresholds between 0.4 to 1')) - parser.add_argument('--word_length', default='2', choices=['2', '3', '4', '5'], + parser.add_argument('--word_length', default='4', choices=['2', '3', '4', '5'], help='A heuristic of CD-hit. Choose of word size:\n5 for similarity thresholds 0.7 ~ 1.0\n4 for similarity thresholds 0.6 ~ 0.7\n3 for similarity thresholds 0.5 ~ 0.6\n2 for similarity thresholds 0.4 ~ 0.5') - parser.add_argument('--discard', default='1', help='Include only sequences longer than <$discard> for the analysis. (CD-hit uses only sequences that are longer than 10 amino acids. When the analysis includes shorter sequences, this threshold should be lowered. Thus, it is set here to 1 by default.)') + parser.add_argument('--discard', default='4', help='Include only sequences longer than <$discard> for the analysis. (CD-hit uses only sequences that are longer than 10 amino acids. When the analysis includes shorter sequences, this threshold should be lowered. Thus, it is set here to 1 by default.)') + parser.add_argument('--cluster_algorithm_mode', default='1', help='0 - clustered to the first cluster that meet the threshold (fast). 1 - clustered to the most similar cluster (slow)') parser.add_argument('-v', '--verbose', action='store_true', help='Increase output verbosity') args = parser.parse_args() @@ -59,6 +66,9 @@ def cluster_sequences(fasta_file, output_prefix, done_file_path, threshold, word else: logging.basicConfig(level=logging.INFO) logger = logging.getLogger('main') - + + start_time = time() cluster_sequences(args.fasta_file, args.output_prefix, args.done_file_path, - args.threshold, args.word_length, args.discard, sys.argv) + args.threshold, args.word_length, args.discard, args.cluster_algorithm_mode, sys.argv) + end_time = time() + logger.info(f'Done cluster sample name {args.sample_name} (took {measure_time(int(end_time-start_time))}).') diff --git a/motif_inference/create_meme.py b/motif_inference/create_meme.py index 7671cb3..2283ab3 100644 --- a/motif_inference/create_meme.py +++ b/motif_inference/create_meme.py @@ -5,8 +5,10 @@ import sys if os.path.exists('/groups/pupko/orenavr2/'): src_dir = '/groups/pupko/orenavr2/igomeProfilingPipeline/src' -else: +elif os.path.exists('/Users/Oren/Dropbox/Projects/'): src_dir = '/Users/Oren/Dropbox/Projects/gershoni/src' +else: + src_dir = '.' sys.path.insert(0, src_dir) from auxiliaries.pipeline_auxiliaries import verify_file_is_not_empty, load_fasta_to_dict, nnk_table diff --git a/motif_inference/extract_clusters_sequences.py b/motif_inference/extract_clusters_sequences.py index 8609738..2f959a2 100644 --- a/motif_inference/extract_clusters_sequences.py +++ b/motif_inference/extract_clusters_sequences.py @@ -3,8 +3,10 @@ import sys if os.path.exists('/groups/pupko/orenavr2/'): src_dir = '/groups/pupko/orenavr2/igomeProfilingPipeline/src' -else: +elif os.path.exists('/Users/Oren/Dropbox/Projects/'): src_dir = '/Users/Oren/Dropbox/Projects/gershoni/src' +else: + src_dir = '.' sys.path.insert(0, src_dir) from auxiliaries.pipeline_auxiliaries import verify_file_is_not_empty, get_count_from diff --git a/motif_inference/generate_weblogo.py b/motif_inference/generate_weblogo.py index 3f68431..be3b050 100644 --- a/motif_inference/generate_weblogo.py +++ b/motif_inference/generate_weblogo.py @@ -1,5 +1,5 @@ -from weblogolib import * -from corebio.seq import unambiguous_protein_alphabet +from weblogo import * +from weblogo.seq import unambiguous_protein_alphabet import sys import logging logger = logging.getLogger('main') diff --git a/motif_inference/merge_meme_files.py b/motif_inference/merge_meme_files.py index 4c06f02..0961eb4 100644 --- a/motif_inference/merge_meme_files.py +++ b/motif_inference/merge_meme_files.py @@ -1,31 +1,40 @@ import datetime import os import sys +from collections import defaultdict + if os.path.exists('/groups/pupko/orenavr2/'): src_dir = '/groups/pupko/orenavr2/igomeProfilingPipeline/src' -else: +elif os.path.exists('/Users/Oren/Dropbox/Projects/'): src_dir = '/Users/Oren/Dropbox/Projects/gershoni/src' +else: + src_dir = '.' sys.path.insert(0, src_dir) +from auxiliaries.pipeline_auxiliaries import load_table_to_dict import logging logger = logging.getLogger('main') -def merge_meme_files(motif_inference_path, biological_condition, merged_meme_path, done_path, samples_to_skip, argv='no_argv'): +def merge_meme_files(motif_inference_path, biological_condition, merged_meme_path, done_path, samplename2biologicalcondition_path, samples_to_skip, argv='no_argv'): """ :param motif_inference_path: A path in which each folder corresponds to a sample and contains a meme file for the motifs in this sample. :param biological_condition: A biological condition to merge its samples meme files :param meme_file_name: An optional file name for the output file + :param samplename2biologicalcondition_path: A file that connect a sample name to is biological condition :return: A merged meme file at $motif_inference_path/$biological_condition/$meme_file_name """ logger.info(f'{datetime.datetime.now()}: merging meme files of {biological_condition}') memes = [] first_meme = True - for sample_name in sorted(os.listdir(motif_inference_path)): # sorted by sample name + samplename2biologicalcondition = load_table_to_dict(samplename2biologicalcondition_path, + 'Barcode {} belongs to more than one sample_name!!') + list_sample_of_bc=[i for i in samplename2biologicalcondition if samplename2biologicalcondition[i]==biological_condition] + for sample_name in sorted(list_sample_of_bc): # sample name of the specific bc dir_path = os.path.join(motif_inference_path, sample_name) - if not os.path.isdir(dir_path) or biological_condition not in sample_name: + if not os.path.isdir(dir_path): # skip file or folders of non-related biological condition continue if sample_name in samples_to_skip: @@ -69,6 +78,7 @@ def merge_meme_files(motif_inference_path, biological_condition, merged_meme_pat parser.add_argument('biological_condition', help='A biological condition to merge its samples meme files') parser.add_argument('merged_meme_path', help='A path to the output file') parser.add_argument('done_file_path', help='A path to a file that signals that the script finished running successfully.') + parser.add_argument('samplename2biologicalcondition_path', help='A path to the sample name to biological condition file.') parser.add_argument('--skip_sample', default='a_weird_str_that_shouldnt_be_a_sample_name_by_any_chance', help='A sample name that should be skipped, e.g., for testing purposes. More than one sample ' 'name should be separated by commas but no spaces. ' @@ -84,4 +94,4 @@ def merge_meme_files(motif_inference_path, biological_condition, merged_meme_pat logger = logging.getLogger('main') merge_meme_files(args.motif_inference_path, args.biological_condition, args.merged_meme_path, - args.done_file_path, args.skip_sample.split(','), sys.argv) + args.done_file_path, args.samplename2biologicalcondition_path ,args.skip_sample.split(','), sys.argv) \ No newline at end of file diff --git a/motif_inference/module_wraper.py b/motif_inference/module_wraper.py index 7eeae15..18bbfcb 100644 --- a/motif_inference/module_wraper.py +++ b/motif_inference/module_wraper.py @@ -1,17 +1,61 @@ import datetime import os import sys +import json if os.path.exists('/groups/pupko/orenavr2/'): src_dir = '/groups/pupko/orenavr2/igomeProfilingPipeline/src' -else: +elif os.path.exists('/Users/Oren/Dropbox/Projects/'): src_dir = '/Users/Oren/Dropbox/Projects/gershoni/src' +else: + src_dir = '.' sys.path.insert(0, src_dir) -from auxiliaries.pipeline_auxiliaries import * +from auxiliaries.pipeline_auxiliaries import get_cluster_rank_from, wait_for_results, submit_pipeline_step, \ + count_memes, load_table_to_dict, fetch_cmd, process_params +from auxiliaries.stop_machine_aws import stop_machines +from auxiliaries.validation_files import is_input_files_valid + +map_names_command_line = { + "parsed_fastq_results" : "reads_path", + "motif_inference_results" : "motifs_path", + "logs_dir" : "logs_dir", + "samplename2biologicalcondition_path" : "sample2bc", + "done_file_path" : "done_file_path", + "max_msas_per_sample" : "max_msas_per_sample", + "max_msas_per_bc" : "max_msas_per_bc", + "max_number_of_cluster_members_per_sample" : "max_num_of_cluster_per_sample", + "max_number_of_cluster_members_per_bc" : "max_num_of_cluster_per_bc", + "allowed_gap_frequency" : "gap", + "multi_exp_config_inference" : "multi_exp_config_inference", + "check_files_valid" : "check_files_valid", + "minimal_number_of_columns_required_create_meme" : "min_num_of_columns_meme", + "prefix_length_in_clstr" : "prefix_length_in_clstr", + "aln_cutoff" : "aln_cutoff", + "pcc_cutoff" : "pcc_cutoff", + "sort_cluster_to_combine_only_by_cluster_size" : "sort_cluster_to_combine_only_by_cluster_size", + "min_number_samples_build_cluster_per_BC" : "min_number_samples_build_cluster_per_BC", + "threshold" : "threshold", + "word_length" : "word_length", + "discard" : "discard", + "cluster_algorithm_mode" :"cluster_alg_mode", + "concurrent_cutoffs" : "concurrent_cutoffs", + "meme_split_size" : "meme_split_size", + "skip_sample_merge_meme" : "skip_sample_merge_meme", + "stop_machines" : "stop_machines_flag", + "type_machines_to_stop" : "type_machines_to_stop", + "name_machines_to_stop" : "name_machines_to_stop", + "cutoff_random_peptitdes_percentile": "cutoff_random_peptitdes_percentile", + "min_library_length_cutoff": "min_library_length_cutoff", + "max_library_length_cutoff": "max_library_length_cutoff", + "error_path" : "error_path", + "queue" : "queue", + "verbose" : "verbose", + "mapitope" : "mapitope" +} def align_clean_pssm_weblogo(folder_names_to_handle, max_clusters_to_align, gap_frequency, - motif_inference_output_path, logs_dir, error_path, queue_name, verbose, data_type): + motif_inference_output_path, logs_dir, minimal_number_of_columns_required_create_meme, error_path, queue_name, verbose, data_type): # For each sample, align each cluster logger.info('_' * 100) logger.info(f'{datetime.datetime.now()}: aligning clusters in each sample') @@ -21,7 +65,6 @@ def align_clean_pssm_weblogo(folder_names_to_handle, max_clusters_to_align, gap_ msas_paths = [] # keep all msas' paths for the next step num_of_cmds_per_job = 33 all_cmds_params = [] # a list of lists. Each sublist contain different parameters set for the same script to reduce the total number of jobs - max_number_of_leading_zeros = len(str(max_clusters_to_align)) logger.info(f'folder_names_to_handle:\n{folder_names_to_handle}') for folder in folder_names_to_handle: path = os.path.join(motif_inference_output_path, folder, 'unaligned_sequences') @@ -38,23 +81,30 @@ def align_clean_pssm_weblogo(folder_names_to_handle, max_clusters_to_align, gap_ cluster_rank = get_cluster_rank_from(faa_filename) aligned_cluster_path = os.path.join(aligned_sequences_path, faa_filename) done_path = f'{logs_dir}/05_{sample_name}_{cluster_rank}_{done_path_suffix}' - all_cmds_params.append([unaligned_cluster_path, aligned_cluster_path, done_path]) - - for i in range(0, len(all_cmds_params), num_of_cmds_per_job): - current_batch = all_cmds_params[i: i + num_of_cmds_per_job] - sample_name = current_batch[0][1].split('/')[-3] - assert sample_name in folder_names_to_handle, f'Sample {sample_name} not in folder names list:\n{folder_names_to_handle}' - cluster_rank = get_cluster_rank_from(current_batch[-1][1]) - cmd = submit_pipeline_step(f'{src_dir}/motif_inference/{script_name}', - current_batch, - logs_dir, f'{sample_name}_{cluster_rank}_msa', - queue_name, verbose) - - num_of_expected_results += len(current_batch) - - - wait_for_results(script_name, logs_dir, num_of_expected_results, example_cmd=cmd, - error_file_path=error_path, suffix=done_path_suffix) + if not os.path.exists(done_path): + all_cmds_params.append([unaligned_cluster_path, aligned_cluster_path, done_path]) + else: + logger.debug(f'Skipping sequence alignment as {done_path} exists') + num_of_expected_results += 1 + + if len(all_cmds_params) > 0: + for i in range(0, len(all_cmds_params), num_of_cmds_per_job): + current_batch = all_cmds_params[i: i + num_of_cmds_per_job] + sample_name = current_batch[0][1].split('/')[-3] + assert sample_name in folder_names_to_handle, f'Sample {sample_name} not in folder names list:\n{folder_names_to_handle}' + cluster_rank = get_cluster_rank_from(current_batch[-1][1]) + cmd = submit_pipeline_step(f'{src_dir}/motif_inference/{script_name}', + current_batch, + logs_dir, f'{sample_name}_{cluster_rank}_msa', + queue_name, verbose) + + num_of_expected_results += len(current_batch) + + + wait_for_results(script_name, logs_dir, num_of_expected_results, example_cmd=cmd, + error_file_path=error_path, suffix=done_path_suffix) + else: + logger.info('Skipping sequence alignment, all found') # For each sample, clean alignments from gappy columns @@ -78,23 +128,29 @@ def align_clean_pssm_weblogo(folder_names_to_handle, max_clusters_to_align, gap_ msa_path = os.path.join(msas_path, msa_name) cleaned_msa_path = os.path.join(cleaned_msas_path, msa_name) done_path = f'{logs_dir}/06_{sample_name}_{msa_name}_{done_path_suffix}' - # done_files_list.append(done_path) - all_cmds_params.append([msa_path, cleaned_msa_path, done_path, - '--maximal_gap_frequency_allowed_per_column', gap_frequency]) - - for i in range(0, len(all_cmds_params), num_of_cmds_per_job): - current_batch = all_cmds_params[i: i + num_of_cmds_per_job] - sample_name = current_batch[0][1].split('/')[-3] - assert sample_name in folder_names_to_handle, f'Sample {sample_name} not in folder names list:\n{folder_names_to_handle}' - cluster_rank = get_cluster_rank_from(current_batch[-1][0]) - cmd = submit_pipeline_step(f'{src_dir}/motif_inference/{script_name}', - current_batch, - logs_dir, f'{sample_name}_{cluster_rank}_clean', - queue_name, verbose) - num_of_expected_results += len(current_batch) - - wait_for_results(script_name, logs_dir, num_of_expected_results, example_cmd=cmd, - error_file_path=error_path, suffix=done_path_suffix) #, done_files_list=done_files_list) + if not os.path.exists(done_path): + all_cmds_params.append([msa_path, cleaned_msa_path, done_path, + '--maximal_gap_frequency_allowed_per_column', gap_frequency]) + else: + logger.debug(f'Skipping cleaning as {done_path} exists') + num_of_expected_results += 1 + + if len(all_cmds_params) > 0: + for i in range(0, len(all_cmds_params), num_of_cmds_per_job): + current_batch = all_cmds_params[i: i + num_of_cmds_per_job] + sample_name = current_batch[0][1].split('/')[-3] + assert sample_name in folder_names_to_handle, f'Sample {sample_name} not in folder names list:\n{folder_names_to_handle}' + cluster_rank = get_cluster_rank_from(current_batch[-1][0]) + cmd = submit_pipeline_step(f'{src_dir}/motif_inference/{script_name}', + current_batch, + logs_dir, f'{sample_name}_{cluster_rank}_clean', + queue_name, verbose) + num_of_expected_results += len(current_batch) + + wait_for_results(script_name, logs_dir, num_of_expected_results, example_cmd=cmd, + error_file_path=error_path, suffix=done_path_suffix) #, done_files_list=done_files_list) + else: + logger.info(f'Skipping cleaning, all exists') # For each sample, generate a meme file with a corresponding pssm for each alignment @@ -103,7 +159,7 @@ def align_clean_pssm_weblogo(folder_names_to_handle, max_clusters_to_align, gap_ script_name = 'create_meme.py' meme_done_path_suffix = f'done_meme_{data_type}.txt' num_of_expected_memes = 0 - num_of_cmds_per_job = 4 + num_of_cmds_per_job = 1 all_cmds_params = [] # a list of lists. Each sublist contain different parameters set for the same script to reduce the total number of jobs for cleaned_msas_path in cleaned_msas_paths: sample_motifs_dir = os.path.split(cleaned_msas_path)[0] @@ -111,14 +167,20 @@ def align_clean_pssm_weblogo(folder_names_to_handle, max_clusters_to_align, gap_ assert sample_name in folder_names_to_handle, f'Sample {sample_name} not in folder names list:\n{folder_names_to_handle}' meme_path = os.path.join(sample_motifs_dir, 'meme.txt') done_path = f'{logs_dir}/07_{sample_name}_{meme_done_path_suffix}' - all_cmds_params.append([cleaned_msas_path, meme_path, done_path]) - for i in range(0, len(all_cmds_params), num_of_cmds_per_job): - current_batch = all_cmds_params[i: i + num_of_cmds_per_job] - memes_cmd = submit_pipeline_step(f'{src_dir}/motif_inference/{script_name}', - current_batch, - logs_dir, f'{i//num_of_cmds_per_job}_meme', - queue_name, verbose) - num_of_expected_memes += len(current_batch) + if not os.path.exists(done_path): + all_cmds_params.append([cleaned_msas_path, meme_path, done_path, f'--minimal_number_of_columns_required {minimal_number_of_columns_required_create_meme}']) + else: + logger.debug(f'Skipping meme creation as {done_path} exists') + num_of_expected_results += 1 + + if len(all_cmds_params) > 0: + for i in range(0, len(all_cmds_params), num_of_cmds_per_job): + current_batch = all_cmds_params[i: i + num_of_cmds_per_job] + memes_cmd = submit_pipeline_step(f'{src_dir}/motif_inference/{script_name}', + current_batch, + logs_dir, f'{i//num_of_cmds_per_job}_meme', + queue_name, verbose) + num_of_expected_memes += len(current_batch) # instead of waiting here, submit the weblogos first.. @@ -134,34 +196,195 @@ def align_clean_pssm_weblogo(folder_names_to_handle, max_clusters_to_align, gap_ for msa_name in os.listdir(cleaned_msas_path): msa_path = os.path.join(cleaned_msas_path, msa_name) weblogo_prefix_path = os.path.join(weblogos_path, os.path.splitext(msa_name)[0]) - all_cmds_params.append([msa_path, weblogo_prefix_path]) + # all_cmds_params.append([msa_path, weblogo_prefix_path]) + + if len(all_cmds_params) > 0: + for i in range(0, len(all_cmds_params), num_of_cmds_per_job): + current_batch = all_cmds_params[i: i + num_of_cmds_per_job] - for i in range(0, len(all_cmds_params), num_of_cmds_per_job): - current_batch = all_cmds_params[i: i + num_of_cmds_per_job] - submit_pipeline_step(f'{src_dir}/motif_inference/{script_name}', - current_batch, - logs_dir, f'weblogo_{i}th_batch', - queue_name='pupkolab', verbose=verbose) # wait for the memes!! (previous logical block!) # (no need to wait for weblogos..) - wait_for_results(script_name, logs_dir, num_of_expected_memes, example_cmd=memes_cmd, - error_file_path=error_path, suffix=meme_done_path_suffix) + if num_of_expected_memes > 0: + script_name = 'create_meme.py' + wait_for_results(script_name, logs_dir, num_of_expected_memes, example_cmd=memes_cmd, + error_file_path=error_path, suffix=meme_done_path_suffix) + else: + logger.info('Skipping memes creation, all found') + + +def compute_cutoffs_then_split(biological_conditions, meme_split_size, cutoff_random_peptitdes_percentile, min_library_length_cutoff, max_library_length_cutoff, + motif_inference_output_path, logs_dir, queue_name, error_path, verbose): + # Compute pssm cutoffs for each bc + logger.info('_'*100) + logger.info(f'{datetime.datetime.now()}: computing pssms cutoffs for the following biological conditions:\n' + f'{biological_conditions}') + script_name = 'calculate_pssm_cutoffs.py' + num_of_expected_results = 0 + all_cmds_params = [] # a list of lists. Each sublist contain different parameters set for the same script to reduce the total number of jobs + for bc in biological_conditions: + bc_folder = os.path.join(motif_inference_output_path, bc) + meme_path = os.path.join(bc_folder, 'meme.txt') + output_path = os.path.join(bc_folder, 'cutoffs.txt') + done_path = f'{logs_dir}/13_{bc}_done_compute_cutoffs.txt' + if not os.path.exists(done_path): + all_cmds_params.append([meme_path, output_path, done_path, '--total_memes', 0, '--cutoff_random_peptitdes_percentile', cutoff_random_peptitdes_percentile, + '--min_library_length_cutoff', min_library_length_cutoff, '--max_library_length_cutoff', max_library_length_cutoff]) + else: + logger.debug(f'Skipping cutoff as {done_path} exists') + num_of_expected_results += 1 + + if len(all_cmds_params) > 0: + for cmds_params, bc in zip(all_cmds_params, biological_conditions): + cmd = submit_pipeline_step(f'{src_dir}/motif_inference/{script_name}', + [cmds_params], + logs_dir, f'{bc}_cutoffs', + queue_name, verbose) + num_of_expected_results += 1 # a single job for each biological condition + + wait_for_results(script_name, logs_dir, num_of_expected_results, example_cmd=cmd, + error_file_path=error_path, suffix='_done_compute_cutoffs.txt') + else: + logger.info('Skipping cuttoffs, all exist') + + # Split memes and cutoffs + logger.info('_'*100) + logger.info(f'{datetime.datetime.now()}: splitting pssms and cutoffs for paralellizing p-values step:\n' + f'{biological_conditions}') + script_name = 'split_meme_and_cutoff_files.py' + num_of_expected_results = 0 + all_cmds_params = [] # a list of lists. Each sublist contain different parameters set for the same script to reduce the total number of jobs + for bc in biological_conditions: + bc_folder = os.path.join(motif_inference_output_path, bc) + meme_path = os.path.join(bc_folder, 'meme.txt') + cutoff_path = os.path.join(bc_folder, 'cutoffs.txt') + done_path = f'{logs_dir}/14_{bc}_done_split.txt' + if not os.path.exists(done_path): + all_cmds_params.append([meme_path, cutoff_path, done_path, '--motifs_per_file', meme_split_size]) + else: + logger.debug(f'Skipping split as {done_path} exists') + num_of_expected_results += 1 + + if len(all_cmds_params) > 0: + for cmds_params, bc in zip(all_cmds_params, biological_conditions): + cmd = submit_pipeline_step(f'{src_dir}/motif_inference/{script_name}', + [cmds_params], + logs_dir, f'{bc}_split', + queue_name, verbose) + num_of_expected_results += 1 # a single job for each biological condition + + wait_for_results(script_name, logs_dir, num_of_expected_results, example_cmd=cmd, + error_file_path=error_path, suffix='_done_split.txt') + else: + logger.info('Skipping split, all exist') -def infer_motifs(first_phase_output_path, max_msas_per_sample, max_msas_per_bc, - max_number_of_cluster_members_per_sample, max_number_of_cluster_members_per_bc, - gap_frequency, motif_inference_output_path, logs_dir, samplename2biologicalcondition_path, - motif_inference_done_path, queue_name, verbose, error_path, argv): +def split_then_compute_cutoffs(biological_conditions, meme_split_size, cutoff_random_peptitdes_percentile, min_library_length_cutoff, max_library_length_cutoff, + motif_inference_output_path, logs_dir, queue_name, error_path, verbose): + # Count memes per BC (synchrnous) + memes_per_bc = {} + for bc in biological_conditions: + bc_folder = os.path.join(motif_inference_output_path, bc) + meme_path = os.path.join(bc_folder, 'meme.txt') + memes_count = count_memes(meme_path) + memes_per_bc[bc] = memes_count - os.makedirs(motif_inference_output_path, exist_ok=True) + # Split memes and cutoffs + logger.info('_'*100) + logger.info(f'{datetime.datetime.now()}: splitting pssms and cutoffs for paralellizing p-values step:\n' + f'{biological_conditions}') + script_name = 'split_meme_and_cutoff_files.py' + num_of_expected_results = 0 + all_cmds_params = [] # a list of lists. Each sublist contain different parameters set for the same script to reduce the total number of jobs + for bc in biological_conditions: + bc_folder = os.path.join(motif_inference_output_path, bc) + meme_path = os.path.join(bc_folder, 'meme.txt') + cutoff_path = 'skip' + done_path = f'{logs_dir}/13_{bc}_done_split.txt' + if not os.path.exists(done_path): + all_cmds_params.append([meme_path, cutoff_path, done_path, '--motifs_per_file', meme_split_size]) + else: + logger.debug(f'Skipping split as {done_path} exists') + num_of_expected_results += 1 + + if len(all_cmds_params) > 0: + for cmds_params, bc in zip(all_cmds_params, biological_conditions): + cmd = submit_pipeline_step(f'{src_dir}/motif_inference/{script_name}', + [cmds_params], + logs_dir, f'{bc}_split', + queue_name, verbose) + num_of_expected_results += 1 # a single job for each biological condition + + wait_for_results(script_name, logs_dir, num_of_expected_results, example_cmd=cmd, + error_file_path=error_path, suffix='_done_split.txt') + else: + logger.info('Skipping split, all exist') + + # Compute pssm cutoffs for each bc + # TODO change to read to cut files (read directory) + logger.info('_'*100) + logger.info(f'{datetime.datetime.now()}: computing pssms cutoffs for the following biological conditions:\n' + f'{biological_conditions}') + script_name = 'calculate_pssm_cutoffs.py' + num_of_expected_results = 0 + all_cmds_params = [] # a list of lists. Each sublist contain different parameters set for the same script to reduce the total number of jobs + cutoffs_bcs = [] + for bc in biological_conditions: + bc_folder = os.path.join(motif_inference_output_path, bc) + bc_memes_folder = os.path.join(bc_folder, 'memes') + bc_cutoffs_folder = os.path.join(bc_folder, 'cutoffs') + os.makedirs(bc_cutoffs_folder, exist_ok=True) + for file_name in sorted(os.listdir(bc_memes_folder)): + meme_path = os.path.join(bc_memes_folder, file_name) + output_path = os.path.join(bc_cutoffs_folder, file_name) + done_path = f'{logs_dir}/14_{bc}_{file_name}_done_compute_cutoffs.txt' + if not os.path.exists(done_path): + all_cmds_params.append([meme_path, output_path, done_path, '--total_memes', memes_per_bc[bc], '--cutoff_random_peptitdes_percentile', cutoff_random_peptitdes_percentile, + '--min_library_length_cutoff', min_library_length_cutoff, '--max_library_length_cutoff', max_library_length_cutoff]) + cutoffs_bcs.append(bc) + else: + logger.debug(f'Skipping calculate cutoff as {done_path} exists') + num_of_expected_results += 1 + + if len(all_cmds_params) > 0: + for cmds_params, bc in zip(all_cmds_params, cutoffs_bcs): + cmd = submit_pipeline_step(f'{src_dir}/motif_inference/{script_name}', + [cmds_params], + logs_dir, f'{bc}_cutoffs', + queue_name, verbose) + num_of_expected_results += 1 # a single job for each biological condition + + wait_for_results(script_name, logs_dir, num_of_expected_results, example_cmd=cmd, + error_file_path=error_path, suffix='_done_compute_cutoffs.txt') + else: + logger.info('Skipping calculate cutoffs, all exists') + + +def infer_motifs(reads_path, motifs_path, logs_dir, sample2bc, + max_msas_per_sample, max_msas_per_bc, max_num_of_cluster_per_sample, max_num_of_cluster_per_bc, + gap, done_file_path, check_files_valid, multi_exp_config_inference, + min_num_of_columns_meme, prefix_length_in_clstr, aln_cutoff, pcc_cutoff, + sort_cluster_to_combine_only_by_cluster_size, min_number_samples_build_cluster_per_BC, + threshold, word_length, discard, cluster_alg_mode, concurrent_cutoffs, meme_split_size, skip_sample_merge_meme, + stop_machines_flag, type_machines_to_stop, name_machines_to_stop, cutoff_random_peptitdes_percentile, + min_library_length_cutoff, max_library_length_cutoff, queue, verbose, mapitope, error_path, exp_name, argv): + + if exp_name: + logger.info(f'{datetime.datetime.now()}: Start motif inference step for experiments {exp_name}') + + if check_files_valid and not is_input_files_valid(samplename2biologicalcondition_path=sample2bc, barcode2samplename_path='', logger=logger): + return + + os.makedirs(motifs_path, exist_ok=True) os.makedirs(logs_dir, exist_ok=True) - if os.path.exists(motif_inference_done_path): - logger.info(f'{datetime.datetime.now()}: skipping motif_inference step ({motif_inference_done_path} already exists)') + if os.path.exists(done_file_path): + logger.info(f'{datetime.datetime.now()}: skipping motif_inference step ({done_file_path} already exists)') return - samplename2biologicalcondition = load_table_to_dict(samplename2biologicalcondition_path, 'Barcode {} belongs to more than one sample!!') + error_path = error_path or os.path.join(motifs_path, 'error.txt') + + samplename2biologicalcondition = load_table_to_dict(sample2bc, 'Barcode {} belongs to more than one sample!!') sample_names = sorted(samplename2biologicalcondition) biological_conditions = sorted(set(samplename2biologicalcondition.values())) @@ -172,31 +395,32 @@ def infer_motifs(first_phase_output_path, max_msas_per_sample, max_msas_per_bc, num_of_expected_results = 0 upper_faa_paths = [] # keep all faas' paths for the next step for sample_name in sample_names: - dir_path = os.path.join(first_phase_output_path, sample_name) + dir_path = os.path.join(reads_path, sample_name) assert os.path.exists(dir_path), f'reads filtration directory does not exist!\n{dir_path}' if not os.path.isdir(dir_path): # skip files or folders of non-related biological condition continue for file_name in os.listdir(dir_path): - if file_name.endswith('faa'): + if file_name.endswith('faa') and 'unique' in file_name and ('mapitope' in file_name) == mapitope: faa_filename = file_name break else: raise ValueError(f'No faa file at {dir_path}') - in_faa_path = os.path.join(first_phase_output_path, sample_name, faa_filename) - out_faa_dir = os.path.join(motif_inference_output_path, sample_name) + in_faa_path = os.path.join(reads_path, sample_name, faa_filename) + out_faa_dir = os.path.join(motifs_path, sample_name) os.makedirs(out_faa_dir, exist_ok=True) out_faa_path = os.path.join(out_faa_dir, f'{sample_name}_upper{faa_filename.split("unique")[-1]}') # not unique anymore (q->Q) upper_faa_paths.append(out_faa_path) done_path = f'{logs_dir}/01_{sample_name}_done_uppering.txt' fetch_cmd(f'{src_dir}/motif_inference/{script_name}', - [in_faa_path, out_faa_path, done_path], verbose, error_path) + [in_faa_path, out_faa_path, done_path], + verbose, error_path, done_path) num_of_expected_results += 1 wait_for_results(script_name, logs_dir, num_of_expected_results, - error_file_path=error_path, suffix='uppering.txt') + error_file_path=error_path, suffix='uppering.txt') # Remove flanking Cysteines before clustering logger.info('_'*100) @@ -211,11 +435,12 @@ def infer_motifs(first_phase_output_path, max_msas_per_sample, max_msas_per_bc, sample_name = upper_faa_path.split('/')[-1].split('_upper_')[0] done_path = f'{logs_dir}/02_{sample_name}_remove_cysteines.txt' fetch_cmd(f'{src_dir}/motif_inference/{script_name}', - [upper_faa_path, no_cys_faa_path, done_path], verbose, error_path) + [upper_faa_path, no_cys_faa_path, done_path], + verbose, error_path, done_path) num_of_expected_results += 1 wait_for_results(script_name, logs_dir, num_of_expected_results, - error_file_path=error_path, suffix='remove_cysteines.txt') + error_file_path=error_path, suffix='remove_cysteines.txt') # Clustering sequences within each sample logger.info('_'*100) @@ -232,19 +457,28 @@ def infer_motifs(first_phase_output_path, max_msas_per_sample, max_msas_per_bc, output_prefix = os.path.join(faa_dir, sample_name) clstr_paths.append(f'{output_prefix}.clstr') done_path = f'{logs_dir}/03_{sample_name}_done_clustering.txt' - all_cmds_params.append([no_cys_faa_path, output_prefix, done_path]) - - for i in range(0, len(all_cmds_params), num_of_cmds_per_job): - current_batch = all_cmds_params[i: i + num_of_cmds_per_job] - sample_name = os.path.split(current_batch[0][1])[-1] - assert sample_name in sample_names, f'Sample {sample_name} not in sample names list:\n{sample_names}' - cmd = submit_pipeline_step(f'{src_dir}/motif_inference/{script_name}', - current_batch, - logs_dir, f'{sample_name}_cluster', queue_name, verbose) - num_of_expected_results += len(current_batch) - - wait_for_results(script_name, logs_dir, num_of_expected_results, example_cmd=cmd, - error_file_path=error_path, suffix='clustering.txt') + if not os.path.exists(done_path): + cmds = [no_cys_faa_path, output_prefix, done_path, sample_name, '--threshold', threshold, '--word_length', word_length, + '--discard', discard, '--cluster_algorithm_mode', cluster_alg_mode] + all_cmds_params.append(cmds) + else: + logger.debug(f'Skipping clustering as {done_path} exists') + num_of_expected_results += 1 + + if len(all_cmds_params) > 0: + for i in range(0, len(all_cmds_params), num_of_cmds_per_job): + current_batch = all_cmds_params[i: i + num_of_cmds_per_job] + sample_name = os.path.split(current_batch[0][1])[-1] + assert sample_name in sample_names, f'Sample {sample_name} not in sample names list:\n{sample_names}' + cmd = submit_pipeline_step(f'{src_dir}/motif_inference/{script_name}', + current_batch, + logs_dir, f'{sample_name}_cluster', queue, verbose) + num_of_expected_results += len(current_batch) + + wait_for_results(script_name, logs_dir, num_of_expected_results, example_cmd=cmd, + error_file_path=error_path, suffix='clustering.txt') + else: + logger.info('Skipping clustering, all exists') # For each sample, split the faa file to the clusters inferred in the previous step # this step uses the sequences WITH THE FLANKING CYSTEINE so the msa will use these Cs @@ -263,25 +497,34 @@ def infer_motifs(first_phase_output_path, max_msas_per_sample, max_msas_per_bc, unaligned_clusters_folders.append(clusters_sequences_path) os.makedirs(clusters_sequences_path, exist_ok=True) done_path = f'{logs_dir}/04_{sample_name}_done_extracting_sequences.txt' - all_cmds_params.append([upper_faa_path, clstr_path, max_number_of_cluster_members_per_sample, clusters_sequences_path, done_path, - f'--file_prefix {sample_name}']) - - for i in range(0, len(all_cmds_params), num_of_cmds_per_job): - current_batch = all_cmds_params[i: i + num_of_cmds_per_job] - clusters_sequences_path = current_batch[0][3] - sample_name = clusters_sequences_path.split('/')[-2] - assert sample_name in sample_names, f'Sample {sample_name} not in sample names list:\n{sample_names}' - cmd = submit_pipeline_step(f'{src_dir}/motif_inference/{script_name}', - current_batch, - logs_dir, f'{sample_name}_extracting_sequences', queue_name, verbose) - num_of_expected_results += len(current_batch) - - wait_for_results(script_name, logs_dir, num_of_expected_results, example_cmd=cmd, - error_file_path=error_path, suffix='extracting_sequences.txt') + if not os.path.exists(done_path): + all_cmds_params.append([upper_faa_path, clstr_path, clusters_sequences_path, done_path, + f'--max_num_of_sequences_to_keep {max_num_of_cluster_per_sample}', + f'--prefix_length_in_clstr {prefix_length_in_clstr}', + f'--file_prefix {sample_name}']) + else: + logger.debug(f'Skipping sequence extraction as {done_path} exists') + num_of_expected_results += 1 + + if len(all_cmds_params) > 0: + for i in range(0, len(all_cmds_params), num_of_cmds_per_job): + current_batch = all_cmds_params[i: i + num_of_cmds_per_job] + clusters_sequences_path = current_batch[0][2] + sample_name = clusters_sequences_path.split('/')[-2] + assert sample_name in sample_names, f'Sample {sample_name} not in sample names list:\n{sample_names}' + cmd = submit_pipeline_step(f'{src_dir}/motif_inference/{script_name}', + current_batch, + logs_dir, f'{sample_name}_extracting_sequences', queue, verbose) + num_of_expected_results += len(current_batch) + + wait_for_results(script_name, logs_dir, num_of_expected_results, example_cmd=cmd, + error_file_path=error_path, suffix='extracting_sequences.txt') + else: + logger.info('Skipping sequences extraction, all exist') # 3 steps together!! align each cluster; clean each alignment; calculate pssm for each alignment - align_clean_pssm_weblogo(sample_names, max_msas_per_sample, gap_frequency, - motif_inference_output_path, logs_dir, error_path, queue_name, verbose, 'samples') + align_clean_pssm_weblogo(sample_names, max_msas_per_sample, gap, + motifs_path, logs_dir, min_num_of_columns_meme, error_path, queue, verbose, 'samples') # Merge memes of the same biological condition logger.info('_'*100) @@ -293,21 +536,28 @@ def infer_motifs(first_phase_output_path, max_msas_per_sample, max_msas_per_bc, all_cmds_params = [] # a list of lists. Each sublist contain different parameters set for the same script to reduce the total number of jobs for bc in biological_conditions: done_path = f'{logs_dir}/08_{bc}_done_meme_merge.txt' - bc_folder = os.path.join(motif_inference_output_path, bc) + bc_folder = os.path.join(motifs_path, bc) os.makedirs(bc_folder, exist_ok=True) output_path = os.path.join(bc_folder, 'merged_meme_sorted.txt') biological_condition_memes.append(output_path) - all_cmds_params.append([motif_inference_output_path, bc, output_path, done_path]) - - for cmds_params, bc in zip(all_cmds_params, biological_conditions): - cmd = submit_pipeline_step(f'{src_dir}/motif_inference/{script_name}', - [cmds_params], - logs_dir, f'{bc}_merge_meme', - queue_name, verbose) - num_of_expected_results += 1 # a single job for each biological condition - - wait_for_results(script_name, logs_dir, num_of_expected_results, example_cmd=cmd, - error_file_path=error_path, suffix='_done_meme_merge.txt') + if not os.path.exists(done_path): + all_cmds_params.append([motifs_path, bc, output_path, done_path, sample2bc, f'--skip_sample {skip_sample_merge_meme}']) + else: + logger.debug(f'Skipping merge as {done_path} exists') + num_of_expected_results += 1 + + if len(all_cmds_params) > 0: + for cmds_params, bc in zip(all_cmds_params, biological_conditions): + cmd = submit_pipeline_step(f'{src_dir}/motif_inference/{script_name}', + [cmds_params], + logs_dir, f'{bc}_merge_meme', + queue, verbose) + num_of_expected_results += 1 # a single job for each biological condition + + wait_for_results(script_name, logs_dir, num_of_expected_results, example_cmd=cmd, + error_file_path=error_path, suffix='_done_meme_merge.txt') + else: + logger.info('Skipping merge, all exist') # Unite motifs based on their correlation using UnitePSSMs.cpp @@ -322,80 +572,47 @@ def infer_motifs(first_phase_output_path, max_msas_per_sample, max_msas_per_bc, relevant_samples = ','.join([sample for sample in samplename2biologicalcondition if samplename2biologicalcondition[sample] == bc]) output_path = os.path.split(merged_meme_path)[0] done_path = f'{logs_dir}/09_{bc}_done_detecting_similar_pssms.txt' - all_cmds_params.append([motif_inference_output_path, merged_meme_path, bc, relevant_samples, - max_number_of_cluster_members_per_bc, - output_path, done_path]) - - for cmds_params, bc in zip(all_cmds_params, biological_conditions): - cmd = submit_pipeline_step(f'{src_dir}/motif_inference/{script_name}', - [cmds_params], - logs_dir, f'{bc}_detect_similar_pssms', - queue_name, verbose) - num_of_expected_results += 1 # a single job for each biological condition - - wait_for_results(script_name, logs_dir, num_of_expected_results, example_cmd=cmd, - error_file_path=error_path, suffix='_done_detecting_similar_pssms.txt') + if not os.path.exists(done_path): + all_cmds_params.append([motifs_path, merged_meme_path, bc, relevant_samples, + max_num_of_cluster_per_bc, + output_path, done_path, f'--aln_cutoff {aln_cutoff}', f'--pcc_cutoff {pcc_cutoff}', + '--sort_cluster_to_combine_only_by_cluster_size' if sort_cluster_to_combine_only_by_cluster_size else '', + f'--min_number_samples_build_cluster_per_BC {min_number_samples_build_cluster_per_BC}']) + else: + logger.debug(f'Skipping unite as {done_path} exists') + num_of_expected_results += 1 + + if len(all_cmds_params) > 0: + for cmds_params, bc in zip(all_cmds_params, biological_conditions): + cmd = submit_pipeline_step(f'{src_dir}/motif_inference/{script_name}', + [cmds_params], + logs_dir, f'{bc}_detect_similar_pssms', + queue, verbose) + num_of_expected_results += 1 # a single job for each biological condition + + wait_for_results(script_name, logs_dir, num_of_expected_results, example_cmd=cmd, + error_file_path=error_path, suffix='_done_detecting_similar_pssms.txt') + else: + logger.info('Skipping unite, all exist') # 3 steps together!! align each cluster; clean each alignment; calculate pssm for each alignment - align_clean_pssm_weblogo(biological_conditions, max_msas_per_bc, gap_frequency, - motif_inference_output_path, logs_dir, error_path, queue_name, verbose, 'biological_conditions') - - - # TODO: do the split BEFORE the cutoffs computation so the cutoff computation can be parallelized!! - # Compute pssm cutoffs for each bc - logger.info('_'*100) - logger.info(f'{datetime.datetime.now()}: computing pssms cuttoffs for the following biological conditions:\n' - f'{biological_conditions}') - script_name = 'calculate_pssm_cutoffs.py' - num_of_expected_results = 0 - all_cmds_params = [] # a list of lists. Each sublist contain different parameters set for the same script to reduce the total number of jobs - for bc in biological_conditions: - bc_folder = os.path.join(motif_inference_output_path, bc) - meme_path = os.path.join(bc_folder, 'meme.txt') - output_path = os.path.join(bc_folder, 'cutoffs.txt') - done_path = f'{logs_dir}/13_{bc}_done_compute_cutoffs.txt' - all_cmds_params.append([meme_path, output_path, done_path]) - - for cmds_params, bc in zip(all_cmds_params, biological_conditions): - cmd = submit_pipeline_step(f'{src_dir}/motif_inference/{script_name}', - [cmds_params], - logs_dir, f'{bc}_cutoffs', - queue_name, verbose) - num_of_expected_results += 1 # a single job for each biological condition - - wait_for_results(script_name, logs_dir, num_of_expected_results, example_cmd=cmd, - error_file_path=error_path, suffix='_done_compute_cutoffs.txt') - - # Split memes and cutoffs - logger.info('_'*100) - logger.info(f'{datetime.datetime.now()}: splitting pssms and cuttoffs for paralellizing p-values step:\n' - f'{biological_conditions}') - script_name = 'split_meme_and_cutoff_files.py' - num_of_expected_results = 0 - all_cmds_params = [] # a list of lists. Each sublist contain different parameters set for the same script to reduce the total number of jobs - for bc in biological_conditions: - bc_folder = os.path.join(motif_inference_output_path, bc) - meme_path = os.path.join(bc_folder, 'meme.txt') - cutoff_path = os.path.join(bc_folder, 'cutoffs.txt') - done_path = f'{logs_dir}/14_{bc}_done_split.txt' - all_cmds_params.append([meme_path, cutoff_path, done_path]) - - for cmds_params, bc in zip(all_cmds_params, biological_conditions): - cmd = submit_pipeline_step(f'{src_dir}/motif_inference/{script_name}', - [cmds_params], - logs_dir, f'{bc}_split', - queue_name, verbose) - num_of_expected_results += 1 # a single job for each biological condition - - wait_for_results(script_name, logs_dir, num_of_expected_results, example_cmd=cmd, - error_file_path=error_path, suffix='_done_split.txt') + align_clean_pssm_weblogo(biological_conditions, max_msas_per_bc, gap, + motifs_path, logs_dir, min_num_of_columns_meme, error_path, queue, verbose, 'biological_conditions') + if concurrent_cutoffs: + split_then_compute_cutoffs(biological_conditions, meme_split_size, cutoff_random_peptitdes_percentile, min_library_length_cutoff, max_library_length_cutoff, + motifs_path, logs_dir, queue, error_path, verbose) + else: + compute_cutoffs_then_split(biological_conditions, meme_split_size, cutoff_random_peptitdes_percentile, min_library_length_cutoff, max_library_length_cutoff, + motifs_path, logs_dir, queue, error_path, verbose) # TODO: fix this bug with a GENERAL WRAPPER done_path # wait_for_results(script_name, num_of_expected_results) - with open(motif_inference_done_path, 'w') as f: + with open(done_file_path, 'w') as f: f.write(' '.join(argv) + '\n') + if stop_machines_flag: + stop_machines(type_machines_to_stop, name_machines_to_stop, logger) if __name__ == '__main__': print(f'Starting {sys.argv[0]}. Executed command is:\n{" ".join(sys.argv)}') @@ -420,10 +637,43 @@ def infer_motifs(first_phase_output_path, max_msas_per_sample, max_msas_per_bc, else parser.error(f'The threshold of the maximal gap frequency allowed per column should be between 0 to 1')) parser.add_argument('done_file_path', help='A path to a file that signals that the module finished running successfully.') + + parser.add_argument('--multi_exp_config_inference', type=str, help='Configuration file to run multi expirements inference phase') + parser.add_argument('--check_files_valid', action='store_true', help='Need to check the validation of the files (samplename2biologicalcondition_path / barcode2samplenaem)') + parser.add_argument('--minimal_number_of_columns_required_create_meme', default=1, type=int, + help='MSAs with less than the number of required columns will be skipped') + parser.add_argument('--prefix_length_in_clstr', default=20, type=int, + help='How long should be the prefix that is taken from the clstr file (cd-hit max prefix is 20)') + parser.add_argument('--aln_cutoff', default='24', help='The cutoff for pairwise alignment score to unite motifs of BC') + parser.add_argument('--pcc_cutoff', default='0.7', help='Minimal PCC R to unite motifs of BC') + parser.add_argument('--sort_cluster_to_combine_only_by_cluster_size', action='store_true', help='Sort the clusters only by the cluster size') + parser.add_argument('--min_number_samples_build_cluster_per_BC', type=str, default=1, help='Keep only clusters that build from X minimum number of samples') + parser.add_argument('--threshold', default='0.6', help='Minimal sequence similarity threshold required', + type=lambda x: float(x) if 0.4 <= float(x) <= 1 + else parser.error(f'CD-hit allows thresholds between 0.4 to 1')) + parser.add_argument('--word_length', default='4', choices=['2', '3', '4', '5'], + help='A heuristic of CD-hit. Choose of word size:\n5 for similarity thresholds 0.7 ~ 1.0\n4 for similarity thresholds 0.6 ~ 0.7\n3 for similarity thresholds 0.5 ~ 0.6\n2 for similarity thresholds 0.4 ~ 0.5') + parser.add_argument('--discard', default='4', help='Include only sequences longer than <$discard> for the analysis. (CD-hit uses only sequences that are longer than 10 amino acids. When the analysis includes shorter sequences, this threshold should be lowered. Thus, it is set here to 1 by default.)') + parser.add_argument('--cluster_algorithm_mode', default='1', type=str, help='0 - clustered to the first cluster that meet the threshold (fast). 1 - clustered to the most similar cluster (slow)') + parser.add_argument('--concurrent_cutoffs', action='store_true', + help='Use new method which splits meme before cutoffs and runs cutoffs concurrently') + parser.add_argument('--meme_split_size', type=int, default=5, + help='Split size, how many meme per files for calculations') + parser.add_argument('--skip_sample_merge_meme', default='a_weird_str_that_shouldnt_be_a_sample_name_by_any_chance', + help='A sample name that should be skipped, e.g., for testing purposes. More than one sample ' + 'name should be separated by commas but no spaces. ' + 'For example: 17b_05,17b_05_test,another_one') + parser.add_argument('--stop_machines', action='store_true', help='Turn off the machines in AWS at the end of the running') + parser.add_argument('--type_machines_to_stop', default='', type=str, help='Type of machines to stop, separated by comma. Empty value means all machines. Example: t2.2xlarge,m5a.24xlarge ') + parser.add_argument('--name_machines_to_stop', default='', type=str, help='Names (patterns) of machines to stop, separated by comma. Empty value means all machines. Example: worker*') + parser.add_argument('--cutoff_random_peptitdes_percentile', type=float, default=0.05, help='Calculate cutoff (hit threshold) from random peptides\' top percentile score') + parser.add_argument('--min_library_length_cutoff', type=int, default=5, help='Minimal value of libraries to generate random peptitdes') + parser.add_argument('--max_library_length_cutoff', type=int, default=14, help='Maximum value of libraries to generate random peptitdes') parser.add_argument('--error_path', type=str, help='a file in which errors will be written to') parser.add_argument('-q', '--queue', default='pupkoweb', type=str, help='a queue to which the jobs will be submitted') parser.add_argument('-v', '--verbose', action='store_true', help='Increase output verbosity') + parser.add_argument('-m', '--mapitope', action='store_true', help='use mapitope encoding') args = parser.parse_args() import logging @@ -433,9 +683,4 @@ def infer_motifs(first_phase_output_path, max_msas_per_sample, max_msas_per_bc, logging.basicConfig(level=logging.WARNING) logger = logging.getLogger('main') - error_path = args.error_path if args.error_path else os.path.join(args.parsed_fastq_results, 'error.txt') - - infer_motifs(args.parsed_fastq_results, args.max_msas_per_sample, args.max_msas_per_bc, - args.max_number_of_cluster_members_per_sample, args.max_number_of_cluster_members_per_bc, - args.allowed_gap_frequency, args.motif_inference_results, args.logs_dir, args.samplename2biologicalcondition_path, - args.done_file_path, args.queue, True if args.verbose else False, error_path, sys.argv) + process_params(args, args.multi_exp_config_inference, map_names_command_line, infer_motifs, 'motif_inference', sys.argv) diff --git a/motif_inference/remove_cysteine_loop.py b/motif_inference/remove_cysteine_loop.py index a5ec5c9..16dfb1f 100644 --- a/motif_inference/remove_cysteine_loop.py +++ b/motif_inference/remove_cysteine_loop.py @@ -4,8 +4,10 @@ import sys if os.path.exists('/groups/pupko/orenavr2/'): src_dir = '/groups/pupko/orenavr2/igomeProfilingPipeline/src' -else: +elif os.path.exists('/Users/Oren/Dropbox/Projects/'): src_dir = '/Users/Oren/Dropbox/Projects/gershoni/src' +else: + src_dir = '.' sys.path.insert(0, src_dir) from auxiliaries.pipeline_auxiliaries import verify_file_is_not_empty diff --git a/motif_inference/remove_gappy_columns.py b/motif_inference/remove_gappy_columns.py index 714ae97..fe3b9b4 100644 --- a/motif_inference/remove_gappy_columns.py +++ b/motif_inference/remove_gappy_columns.py @@ -3,8 +3,10 @@ import sys if os.path.exists('/groups/pupko/orenavr2/'): src_dir = '/groups/pupko/orenavr2/igomeProfilingPipeline/src' -else: +elif os.path.exists('/Users/Oren/Dropbox/Projects/'): src_dir = '/Users/Oren/Dropbox/Projects/gershoni/src' +else: + src_dir = '.' sys.path.insert(0, src_dir) from auxiliaries.pipeline_auxiliaries import verify_file_is_not_empty, load_fasta_to_dict diff --git a/motif_inference/split_meme_and_cutoff_files.py b/motif_inference/split_meme_and_cutoff_files.py index ac66c24..9c940e0 100644 --- a/motif_inference/split_meme_and_cutoff_files.py +++ b/motif_inference/split_meme_and_cutoff_files.py @@ -3,71 +3,88 @@ import sys if os.path.exists('/groups/pupko/orenavr2/'): src_dir = '/groups/pupko/orenavr2/igomeProfilingPipeline/src' -else: +elif os.path.exists('/Users/Oren/Dropbox/Projects/'): src_dir = '/Users/Oren/Dropbox/Projects/gershoni/src' +else: + src_dir = '.' sys.path.insert(0, src_dir) from auxiliaries.pipeline_auxiliaries import verify_file_is_not_empty def split_meme_and_cutoff_files(meme_file_path, cutoffs_file_path, motifs_per_file, done_path, argv='no_argv'): - - verify_file_is_not_empty(meme_file_path) - verify_file_is_not_empty(cutoffs_file_path) - - splitted_meme_dir = os.path.join(os.path.split(meme_file_path)[0], 'memes') - os.makedirs(splitted_meme_dir, exist_ok=True) - - splitted_cutoffs_dir = os.path.join(os.path.split(cutoffs_file_path)[0], 'cutoffs') - os.makedirs(splitted_cutoffs_dir, exist_ok=True) - - logger.info(f'{datetime.datetime.now()}: splitting pssms and cuttoffs to:\n' - f'{splitted_meme_dir}\n' - f'{splitted_cutoffs_dir}') - - with open(meme_file_path) as meme_f: - meta_info = '' - data = '' - motif_number = 0 - split_number = 0 - add_meta_info = True - for line in meme_f: - if add_meta_info: - if "MOTIF" not in line: - meta_info += line - continue - else: - add_meta_info = False - if line.startswith("MOTIF"): - if motif_number == motifs_per_file: - with open(f'{splitted_meme_dir}/{str(split_number).zfill(2)}.txt', 'w') as f: - f.write(meta_info + data) - data = '' - motif_number = 0 - split_number += 1 - motif_number += 1 - data += line - # don't forget last batch!! - with open(f'{splitted_meme_dir}/{str(split_number).zfill(2)}.txt', 'w') as f: - f.write(meta_info + data) - - with open(cutoffs_file_path) as cutoffs_f: - data = '' - motif_number = 0 - split_number = 0 - for line in cutoffs_f: - if line.startswith("###"): - if motif_number == motifs_per_file: - with open(f'{splitted_cutoffs_dir}/{str(split_number).zfill(2)}.txt', 'w') as f: - f.write(data) - data = '' - motif_number = 0 - split_number += 1 - motif_number += 1 - data += line - # don't forget last batch!! - with open(f'{splitted_cutoffs_dir}/{str(split_number).zfill(2)}.txt', 'w') as f: - f.write(data) + if meme_file_path: + verify_file_is_not_empty(meme_file_path) + if cutoffs_file_path: + verify_file_is_not_empty(cutoffs_file_path) + + message = f'{datetime.datetime.now()}: splitting ' + + if meme_file_path: + message += 'pssms' + splitted_meme_dir = os.path.join(os.path.split(meme_file_path)[0], 'memes') + os.makedirs(splitted_meme_dir, exist_ok=True) + + if cutoffs_file_path: + if meme_file_path: message += "and " + message += 'cutoffs to:\n' + splitted_cutoffs_dir = os.path.join(os.path.split(cutoffs_file_path)[0], 'cutoffs') + os.makedirs(splitted_cutoffs_dir, exist_ok=True) + + if meme_file_path: + message += splitted_meme_dir + + if cutoffs_file_path: + if meme_file_path: message += '\n' + message += splitted_cutoffs_dir + + logger.info(message) + + if meme_file_path: + with open(meme_file_path) as meme_f: + meta_info = '' + data = '' + motif_number = 0 + split_number = 0 + add_meta_info = True + for line in meme_f: + if add_meta_info: + if "MOTIF" not in line: + meta_info += line + continue + else: + add_meta_info = False + if line.startswith("MOTIF"): + if motif_number == motifs_per_file: + with open(f'{splitted_meme_dir}/{str(split_number).zfill(2)}.txt', 'w') as f: + f.write(meta_info + data) + data = '' + motif_number = 0 + split_number += 1 + motif_number += 1 + data += line + # don't forget last batch!! + with open(f'{splitted_meme_dir}/{str(split_number).zfill(2)}.txt', 'w') as f: + f.write(meta_info + data) + + if cutoffs_file_path: + with open(cutoffs_file_path) as cutoffs_f: + data = '' + motif_number = 0 + split_number = 0 + for line in cutoffs_f: + if line.startswith("###"): + if motif_number == motifs_per_file: + with open(f'{splitted_cutoffs_dir}/{str(split_number).zfill(2)}.txt', 'w') as f: + f.write(data) + data = '' + motif_number = 0 + split_number += 1 + motif_number += 1 + data += line + # don't forget last batch!! + with open(f'{splitted_cutoffs_dir}/{str(split_number).zfill(2)}.txt', 'w') as f: + f.write(data) with open(done_path, 'w') as f: f.write(' '.join(argv) + '\n') @@ -94,6 +111,9 @@ def split_meme_and_cutoff_files(meme_file_path, cutoffs_file_path, motifs_per_fi logging.basicConfig(level=logging.INFO) logger = logging.getLogger('main') - split_meme_and_cutoff_files(args.meme_file_path, args.cutoffs_file_path, args.motifs_per_file, + meme_file_path = None if args.meme_file_path.lower() == 'skip' else args.meme_file_path + cutoffs_file_path = None if args.cutoffs_file_path.lower() == 'skip' else args.cutoffs_file_path + + split_meme_and_cutoff_files(meme_file_path, cutoffs_file_path, args.motifs_per_file, args.done_file_path, sys.argv) diff --git a/motif_inference/unite_motifs_of_biological_condition.py b/motif_inference/unite_motifs_of_biological_condition.py index 7800a1d..c1c1722 100644 --- a/motif_inference/unite_motifs_of_biological_condition.py +++ b/motif_inference/unite_motifs_of_biological_condition.py @@ -2,15 +2,18 @@ import subprocess import logging import os +import math import sys if os.path.exists('/groups/pupko/orenavr2/'): src_dir = '/groups/pupko/orenavr2/igomeProfilingPipeline/src' -else: +elif os.path.exists('/Users/Oren/Dropbox/Projects/'): src_dir = '/Users/Oren/Dropbox/Projects/gershoni/src' +else: + src_dir = '.' sys.path.insert(0, src_dir) from auxiliaries.pipeline_auxiliaries import verify_file_is_not_empty, get_cluster_size_from_name, load_fasta_to_dict, \ - get_count_from + get_count_from, get_samples_from_name, get_unique_members_from_name def get_clusters_sequences(motif_inference_output_path, biological_condition, sample_names, @@ -57,9 +60,25 @@ def get_clusters_sequences(motif_inference_output_path, biological_condition, sa return result, result_file_name +def remove_consensus_name(clusters): + #remove consensus sequence so we have the exact cluster (file) name + return [cluster[cluster.index('_')+1:] for cluster in clusters] + + +def get_samples_number_build_cluster(clusters): + return len(set([get_samples_from_name(cluster) for cluster in clusters])) + + +def sort_clusters(clusters): + sort_by_num_samples = get_samples_number_build_cluster(clusters) + sort_by_unique_memebers = sum(get_unique_members_from_name(cluster) for cluster in clusters)*(-1) + sort_by_cluster_size = sum(get_cluster_size_from_name(cluster) for cluster in clusters) + return (sort_by_num_samples, sort_by_cluster_size , sort_by_unique_memebers) + def unite_clusters(motif_inference_output_path, meme_file, biological_condition, sample_names, - max_number_of_members_per_cluster, output_path, done_path, aln_cutoff, pcc_cutoff, - unite_pssm_script_path='/groups/pupko/orenavr2/gershoni/src/UnitePSSMs/UnitePSSMs', argv='no_argv'): + max_number_of_members_per_cluster, output_path, done_path, aln_cutoff, pcc_cutoff, + sort_cluster_to_combine_only_by_cluster_size, min_number_samples_build_cluster, + unite_pssm_script_path='./UnitePSSMs/UnitePSSMs', argv='no_argv'): clusters_to_combine_path = os.path.join(output_path, 'cluster_to_combine.csv') if not os.path.exists(clusters_to_combine_path): @@ -77,17 +96,20 @@ def unite_clusters(motif_inference_output_path, meme_file, biological_condition, with open(clusters_to_combine_path) as f: for line in f: cluster_names = line.rstrip().split(',') - # remove consensus sequence so we have the exact cluster (file) name - cluster_without_prefix = [cluster[cluster.index('_')+1:] for cluster in cluster_names] - clusters_to_combine.append(cluster_without_prefix) + clusters_to_combine.append(cluster_names) logger.info(f'Sorting clusters by rank...') # sort the sublist such that the first one will contain the highest copy number, etc... - clusters_to_combine.sort(key=lambda clusters: sum(get_cluster_size_from_name(cluster) for cluster in clusters), reverse=True) + if sort_cluster_to_combine_only_by_cluster_size: + clusters_to_combine.sort(key=lambda clusters: sum(get_cluster_size_from_name(cluster) for cluster in clusters), reverse=True) + else: + clusters_to_combine.sort(key=lambda clusters: sort_clusters(clusters), reverse=True) sorted_clusters_to_combine_path = clusters_to_combine_path.replace('cluster_to_combine', 'sorted_cluster_to_combine') with open(sorted_clusters_to_combine_path, 'w') as f: for cluster_names in clusters_to_combine: - f.write(','.join(cluster_names)+'\n') + num_of_samples_per_cluster = get_samples_number_build_cluster(cluster_names) + if num_of_samples_per_cluster >= min_number_samples_build_cluster: + f.write(','.join(cluster_names)+'\n') unaligned_sequences_path = os.path.join(output_path, 'unaligned_sequences') os.makedirs(unaligned_sequences_path, exist_ok=True) @@ -97,7 +119,7 @@ def unite_clusters(motif_inference_output_path, meme_file, biological_condition, logger.info(f'Merging sequences of the cluster ranked {cluster_rank}') clusters_sequences, cluster_file_name = get_clusters_sequences(motif_inference_output_path, biological_condition, - sample_names, clusters_to_combine[cluster_rank], + sample_names, remove_consensus_name(clusters_to_combine[cluster_rank]), cluster_rank, max_number_of_members_per_cluster) with open(os.path.join(unaligned_sequences_path, cluster_file_name), 'w') as f: f.write(clusters_sequences) @@ -124,8 +146,10 @@ def unite_clusters(motif_inference_output_path, meme_file, biological_condition, help='How many members (at most) should be taken to each cluster') parser.add_argument('output_path', help='A path in which a new subfolder with the united motifs will be written to') parser.add_argument('done_file_path', help='A path to a file that signals that the script finished running successfully.') - parser.add_argument('--aln_cutoff', default='20', help='TODO') # TODO: what do this param do? - parser.add_argument('--pcc_cutoff', default='0.6', help='TODO') # TODO: what do this param do? + parser.add_argument('--aln_cutoff', default='24', help='The cutoff for pairwise alignment score to unite motifs of BC') + parser.add_argument('--pcc_cutoff', default='0.7', help='Minimal PCC R to unite motifs of BC') + parser.add_argument('--sort_cluster_to_combine_only_by_cluster_size', action='store_true', help='Sort the clusters only by the cluster size') + parser.add_argument('--min_number_samples_build_cluster_per_BC', type=int, default=1, help='Keep only clusters that build from X minimum number of samples') parser.add_argument('-v', '--verbose', action='store_true', help='Increase output verbosity') args = parser.parse_args() @@ -137,4 +161,5 @@ def unite_clusters(motif_inference_output_path, meme_file, biological_condition, unite_clusters(args.motif_inference_output_path, args.meme_file_path, args.biological_condition, args.sample_names.split(','), args.max_number_of_members_per_cluster, args.output_path, args.done_file_path, - args.aln_cutoff, args.pcc_cutoff, argv=sys.argv) + args.aln_cutoff, args.pcc_cutoff, args.sort_cluster_to_combine_only_by_cluster_size, + args.min_number_samples_build_cluster_per_BC, argv=sys.argv) diff --git a/motif_inference/upper_case_sequences.py b/motif_inference/upper_case_sequences.py index 6be6ff3..9af7a15 100644 --- a/motif_inference/upper_case_sequences.py +++ b/motif_inference/upper_case_sequences.py @@ -4,8 +4,10 @@ import sys if os.path.exists('/groups/pupko/orenavr2/'): src_dir = '/groups/pupko/orenavr2/igomeProfilingPipeline/src' -else: +elif os.path.exists('/Users/Oren/Dropbox/Projects/'): src_dir = '/Users/Oren/Dropbox/Projects/gershoni/src' +else: + src_dir = '.' sys.path.insert(0, src_dir) from auxiliaries.pipeline_auxiliaries import verify_file_is_not_empty, load_fasta_to_dict diff --git a/old/Auxilaries.py b/old/Auxilaries.py old mode 100755 new mode 100644 diff --git a/old/FilterReads.py b/old/FilterReads.py old mode 100755 new mode 100644 diff --git a/old/JoinDSSamples.py b/old/JoinDSSamples.py old mode 100755 new mode 100644 diff --git a/old/JoinDSSamplesWrapper.py b/old/JoinDSSamplesWrapper.py old mode 100755 new mode 100644 diff --git a/old/NormalizePercentsByBaseline.py b/old/NormalizePercentsByBaseline.py old mode 100755 new mode 100644 diff --git a/old/ParseFastQ.py b/old/ParseFastQ.py old mode 100755 new mode 100644 diff --git a/old/generate_csv_file_for_motifs_inference.py b/old/generate_csv_file_for_motifs_inference.py old mode 100755 new mode 100644 diff --git a/old/generate_heatmap.R b/old/generate_heatmap.R old mode 100755 new mode 100644 diff --git a/rabbit-docker-compose.yml b/rabbit-docker-compose.yml new file mode 100644 index 0000000..c0a6210 --- /dev/null +++ b/rabbit-docker-compose.yml @@ -0,0 +1,15 @@ +version: '3' + +services: + rabbit: + image: rabbitmq:management + restart: always + ports: + - 5672:5672 + - 15672:15672 + flower: + image: mher/flower + restart: always + command: flower --broker=pyamqp://guest@rabbit// + ports: + - 5555:5555 diff --git a/reads_filtration/filter_reads.py b/reads_filtration/filter_reads.py index ae9d42b..c55fd4d 100644 --- a/reads_filtration/filter_reads.py +++ b/reads_filtration/filter_reads.py @@ -4,8 +4,10 @@ import sys if os.path.exists('/groups/pupko/orenavr2/'): src_dir = '/groups/pupko/orenavr2/igomeProfilingPipeline/src' -else: +elif os.path.exists('/Users/Oren/Dropbox/Projects/'): src_dir = '/Users/Oren/Dropbox/Projects/gershoni/src' +else: + src_dir = '.' sys.path.insert(0, src_dir) # needs $src_dir in path @@ -57,6 +59,7 @@ def get_barcodes_dictionaries(barcode_to_samplename, output_dir, gz) -> {str: {s 'too_many_mistakes', 'stop_codon', 'too_short', + 'too_long', 'total_translated_sequences', 'uag'], 0) barcode2statistics[barcode]['lib_type'] = {} @@ -81,19 +84,27 @@ def write_header(f_handler, txt): f_handler.write('_' * 80 + f'\n{txt}') -def filter_reads(argv, fastq_file, parsed_fastq_results, logs_dir, - done_path, barcode2samplename_path, +def filter_reads(argv, fastq_path, parsed_fastq_results, logs_dir, + done_path, barcode2samplename_path, name_summary_file, left_construct, right_construct, max_mismatches_allowed, - min_sequencing_quality, minimal_length_required, gz): + min_sequencing_quality, minimal_length_required, maximum_length_required, gz): start_time = datetime.datetime.now() - from auxiliaries.pipeline_auxiliaries import nnk_table left_construct_length = len(left_construct) right_construct_length = len(right_construct) logger.info(f'{datetime.datetime.now()}: Loading barcode2samplename file from:\n{barcode2samplename_path}') + + if os.path.isdir(fastq_path): + fastq_files = [os.path.join(fastq_path, file) for file in os.listdir(fastq_path) if file.endswith('.fastq.gz')] + total_files = len(fastq_files) + logger.info(f'{total_files} fastq files found: {fastq_files}') + else: + fastq_files = [fastq_path] + total_files = 1 + barcode2samplename = load_table_to_dict(barcode2samplename_path, 'Barcode {} belongs to more than one sample!!') assert len(barcode2samplename) > 0, f'No barcodes were found in {barcode2samplename_path}' # TODO: add informative error to log @@ -107,121 +118,130 @@ def filter_reads(argv, fastq_file, parsed_fastq_results, logs_dir, lib_types = set() # keeps all the library types seen record_num = 0 # so the line number will represent the actual sequence inside each record - with gzip.open(fastq_file, 'rb') as fastq_f: - for line1 in fastq_f: - # ignore first line of each record - line1 = line1.decode("utf-8").rstrip() - # second line contains the read itself - dna_read = fastq_f.readline().decode("utf-8").rstrip() - # ignore third line of each record - line3 = fastq_f.readline().decode("utf-8").rstrip() - # fourth line contains the sequencing quality - quality = fastq_f.readline().decode("utf-8").rstrip() - - - if record_num % 1_000_000 == 0: - logger.info(f'{datetime.datetime.now()}: {record_num} records have been processed...') - - if not line1.startswith('@'): - raise TypeError('NGS file has an invalid format') - - record_num += 1 - - # check that barcode exists - barcode = dna_read[:barcode_len] - if barcode not in barcode2samplename: - continue - - barcode2statistics[barcode]['legal_barcode'] += 1 - - # check that the barcode quality is above the required threshold - barcode_quality = quality[:barcode_len] - lowest_scored_character = min([ord(c) for c in barcode_quality]) - if lowest_scored_character < min_sequencing_quality: - barcode2statistics[barcode]['poor_quality_barcode'] += 1 - barcode2filehandlers[barcode]['filtration_log'].write(f"{barcode2statistics['legal_barcode']}\tlow quality barcode ({lowest_scored_character})\t{dna_read}\n") - continue - barcode2statistics[barcode]['high_quality_barcode'] += 1 - - # verify left construct - current_left_construct = dna_read[barcode_len: barcode_len + left_construct_length] - # if regex.match(f'({current_left_construct})' + '{s<='f"{max_mismatches_allowed}"'}', left_construct) == None: - num_of_mismatches_on_the_left = str_diff(current_left_construct, left_construct) - if num_of_mismatches_on_the_left > max_mismatches_allowed: - barcode2statistics[barcode]['too_many_mistakes'] += 1 - barcode2filehandlers[barcode]['filtration_log'].write(f"Sequence number {barcode2statistics[barcode]['legal_barcode']}\tleft construct has {num_of_mismatches_on_the_left} (max mismatches allowed is: {max_mismatches_allowed})\t{current_left_construct}\n") - continue - - # extract random fragment (according to the currently assumed library) - random_dna_start = barcode_len + left_construct_length - rest_of_read = dna_read[random_dna_start:] - - random_peptide = '' - has_stop_codon = False - q_in_peptide = False - i = 0 - for i in range(0, len(rest_of_read), 3): - codon = rest_of_read[i: i+3] - if codon == "TGA" or codon == "TAA": - has_stop_codon = True - barcode2statistics[barcode]['stop_codon'] += 1 - barcode2filehandlers[barcode]['filtration_log'].write(f"Sequence number {barcode2statistics[barcode]['legal_barcode']}\tcontains a stop codon\t{rest_of_read[i:i+3]}\n") - break - if len(codon) < 3: - break - if codon[0] not in 'ACGT' or codon[1] not in 'ACGT': # unrecognized dna base, e.g., N - break - if codon[2] not in 'GT': # K group (of NNK) = G or T - break - random_peptide += nnk_table[codon] - if codon == 'TAG': - q_in_peptide = True - - if has_stop_codon: - # all set and documented. We can continue to the next read... - continue - - random_dna_end = i # where we stopped seeing NNK - rest_of_read = rest_of_read[random_dna_end:] # current right construct + dna remnants - - # if the right construct is too short, just try to match what it has... - num_of_mismatches_on_the_right = str_diff(rest_of_read, right_construct) - if num_of_mismatches_on_the_right > max_mismatches_allowed - num_of_mismatches_on_the_left: - barcode2statistics[barcode]['too_many_mistakes'] += 1 - barcode2filehandlers[barcode]['filtration_log'].write(f"Sequence number {barcode2statistics[barcode]['legal_barcode']}\tflanking constructs have {num_of_mismatches_on_the_left + num_of_mismatches_on_the_right} (max mismatches allowed is: {max_mismatches_allowed})\t{current_left_construct} {rest_of_read[:right_construct_length]}\n") - # all set and documented. We can continue to the next read... - continue - - if (len(random_peptide) < minimal_length_required or - (random_peptide.startswith('C') and random_peptide.endswith('C') and - len(random_peptide)-2 < minimal_length_required)): # minimum required length (excluding flanking Cysteine) - barcode2statistics[barcode]['too_short'] += 1 - barcode2filehandlers[barcode]['filtration_log'].write(f"Sequence number {barcode2statistics[barcode]['legal_barcode']}\trandom peptide is too short\t{random_peptide}\n") - # all set and documented. We can continue to the next read... - continue - - # reached here? read is valid!! woohoo - barcode2statistics[barcode]['total_translated_sequences'] += 1 - if q_in_peptide: - barcode2statistics[barcode]['uag'] += 1 - - # update library counts - current_lib_type = f'{len(random_peptide)}' - if random_peptide.startswith('C') and random_peptide.endswith('C'): - # A random peptide from a cys loop library - current_lib_type = f'C{len(random_peptide)-2}C' - if current_lib_type not in lib_types: - lib_types.add(current_lib_type) - barcode2statistics[barcode]['lib_type'][current_lib_type] = barcode2statistics[barcode]['lib_type'].get(current_lib_type, 0) + 1 - - # add record to a dedicated fastq file - barcode2filehandlers[barcode]['fastq'].write(f'{line1}\n{dna_read}\n{line3}\n{quality}\n') - # add dna read to a dedicated fna file - barcode2filehandlers[barcode]['fna'].write( - f">Seq_{barcode2statistics[barcode]['legal_barcode']}_Lib_{current_lib_type}\n{dna_read[random_dna_start: random_dna_end]}\n") - # add peptide (translated dna) to a dedicated faa file - barcode2filehandlers[barcode]['faa'].write( - f">Seq_{barcode2statistics[barcode]['legal_barcode']}_Lib_{current_lib_type}\n{random_peptide}\n") + for i, fastq_file in enumerate(fastq_files): + logger.info(f'Reading and filtering {i+1}/{total_files}: {fastq_file}') + with gzip.open(fastq_file, 'rb') as fastq_f: + for line1 in fastq_f: + # ignore first line of each record + line1 = line1.decode("utf-8").rstrip() + # second line contains the read itself + dna_read = fastq_f.readline().decode("utf-8").rstrip() + # ignore third line of each record + line3 = fastq_f.readline().decode("utf-8").rstrip() + # fourth line contains the sequencing quality + quality = fastq_f.readline().decode("utf-8").rstrip() + + + if record_num % 1_000_000 == 0: + logger.info(f'{datetime.datetime.now()}: {record_num} records have been processed...') + + if not line1.startswith('@'): + raise TypeError('NGS file has an invalid format') + + record_num += 1 + + # check that barcode exists + barcode = dna_read[:barcode_len] + if barcode not in barcode2samplename: + continue + + barcode2statistics[barcode]['legal_barcode'] += 1 + + # check that the barcode quality is above the required threshold + barcode_quality = quality[:barcode_len] + lowest_scored_character = min([ord(c) for c in barcode_quality]) + if lowest_scored_character < min_sequencing_quality: + barcode2statistics[barcode]['poor_quality_barcode'] += 1 + barcode2filehandlers[barcode]['filtration_log'].write(f"{barcode2statistics['legal_barcode']}\tlow quality barcode ({lowest_scored_character})\t{dna_read}\n") + continue + barcode2statistics[barcode]['high_quality_barcode'] += 1 + + # verify left construct + current_left_construct = dna_read[barcode_len: barcode_len + left_construct_length] + # if regex.match(f'({current_left_construct})' + '{s<='f"{max_mismatches_allowed}"'}', left_construct) == None: + num_of_mismatches_on_the_left = str_diff(current_left_construct, left_construct) + if num_of_mismatches_on_the_left > max_mismatches_allowed: + barcode2statistics[barcode]['too_many_mistakes'] += 1 + barcode2filehandlers[barcode]['filtration_log'].write(f"Sequence number {barcode2statistics[barcode]['legal_barcode']}\tleft construct has {num_of_mismatches_on_the_left} (max mismatches allowed is: {max_mismatches_allowed})\t{current_left_construct}\n") + continue + + # extract random fragment (according to the currently assumed library) + random_dna_start = barcode_len + left_construct_length + rest_of_read = dna_read[random_dna_start:] + + random_peptide = '' + has_stop_codon = False + q_in_peptide = False + i = 0 + for i in range(0, len(rest_of_read), 3): + codon = rest_of_read[i: i+3] + if codon == "TGA" or codon == "TAA": + has_stop_codon = True + barcode2statistics[barcode]['stop_codon'] += 1 + barcode2filehandlers[barcode]['filtration_log'].write(f"Sequence number {barcode2statistics[barcode]['legal_barcode']}\tcontains a stop codon\t{rest_of_read[i:i+3]}\n") + break + if len(codon) < 3: + break + if codon[0] not in 'ACGT' or codon[1] not in 'ACGT': # unrecognized dna base, e.g., N + break + if codon[2] not in 'GT': # K group (of NNK) = G or T + break + random_peptide += nnk_table[codon] + if codon == 'TAG': + q_in_peptide = True + + if has_stop_codon: + # all set and documented. We can continue to the next read... + continue + + random_dna_end = i # where we stopped seeing NNK + rest_of_read = rest_of_read[random_dna_end:] # current right construct + dna remnants + + # if the right construct is too short, just try to match what it has... + num_of_mismatches_on_the_right = str_diff(rest_of_read, right_construct) + if num_of_mismatches_on_the_right > max_mismatches_allowed - num_of_mismatches_on_the_left: + barcode2statistics[barcode]['too_many_mistakes'] += 1 + barcode2filehandlers[barcode]['filtration_log'].write(f"Sequence number {barcode2statistics[barcode]['legal_barcode']}\tflanking constructs have {num_of_mismatches_on_the_left + num_of_mismatches_on_the_right} (max mismatches allowed is: {max_mismatches_allowed})\t{current_left_construct} {rest_of_read[:right_construct_length]}\n") + # all set and documented. We can continue to the next read... + continue + + if (len(random_peptide) < minimal_length_required or + (random_peptide.startswith('C') and random_peptide.endswith('C') and + len(random_peptide)-2 < minimal_length_required)): # minimum required length (excluding flanking Cysteine) + barcode2statistics[barcode]['too_short'] += 1 + barcode2filehandlers[barcode]['filtration_log'].write(f"Sequence number {barcode2statistics[barcode]['legal_barcode']}\trandom peptide is too short\t{random_peptide}\n") + # all set and documented. We can continue to the next read... + continue + if (len(random_peptide) > maximum_length_required or + (random_peptide.startswith('C') and random_peptide.endswith('C') and + len(random_peptide) - 2 > maximum_length_required)): + barcode2statistics[barcode]['too_long'] += 1 + barcode2filehandlers[barcode]['filtration_log'].write(f"Sequence number {barcode2statistics[barcode]['legal_barcode']}\trandom peptide is too long\t{random_peptide}\n") + # all set and documented. We can continue to the next read... + continue + + # reached here? read is valid!! woohoo + barcode2statistics[barcode]['total_translated_sequences'] += 1 + if q_in_peptide: + barcode2statistics[barcode]['uag'] += 1 + + # update library counts + current_lib_type = f'{len(random_peptide)}' + if random_peptide.startswith('C') and random_peptide.endswith('C'): + # A random peptide from a cys loop library + current_lib_type = f'C{len(random_peptide)-2}C' + if current_lib_type not in lib_types: + lib_types.add(current_lib_type) + barcode2statistics[barcode]['lib_type'][current_lib_type] = barcode2statistics[barcode]['lib_type'].get(current_lib_type, 0) + 1 + + # add record to a dedicated fastq file + barcode2filehandlers[barcode]['fastq'].write(f'{line1}\n{dna_read}\n{line3}\n{quality}\n') + # add dna read to a dedicated fna file + barcode2filehandlers[barcode]['fna'].write( + f">Seq_{barcode2statistics[barcode]['legal_barcode']}_Lib_{current_lib_type}\n{dna_read[random_dna_start: random_dna_end]}\n") + # add peptide (translated dna) to a dedicated faa file + barcode2filehandlers[barcode]['faa'].write( + f">Seq_{barcode2statistics[barcode]['legal_barcode']}_Lib_{current_lib_type}\n{random_peptide}\n") for barcode in barcode2samplename: barcode2filehandlers[barcode]['fastq'].close() @@ -235,19 +255,22 @@ def filter_reads(argv, fastq_file, parsed_fastq_results, logs_dir, with open(barcode2info_filenames[barcode], 'w') as f: f.write(f'Starting {argv[0]}. Executed command is:\n{" ".join(argv)}\n') f.write(f'reads_filtration function is invoked with the following parameters:\n') - f.write(f'fastq_file = {fastq_file}\n' + f.write(f'fastq_path = {fastq_path}\n' f'parsed_fastq_results = {parsed_fastq_results}\n' f'logs_dir = {logs_dir}\n' f'barcode2samplename_path = {barcode2samplename_path}\n' f'left_construct = {left_construct}\n' f'right_construct = {right_construct}\n' f'max_mismatches_allowed = {max_mismatches_allowed}\n' - f'min_sequencing_quality = {min_sequencing_quality}\n') + f'min_sequencing_quality = {min_sequencing_quality}\n' + f'minimal_length_required = {minimal_length_required}\n' + f'maximum_length_required = {maximum_length_required}\n') total_filtered_reads_per_barcode = barcode2statistics[barcode]['poor_quality_barcode'] + \ barcode2statistics[barcode]['too_many_mistakes'] + \ barcode2statistics[barcode]['stop_codon'] + \ - barcode2statistics[barcode]['too_short'] + barcode2statistics[barcode]['too_short'] + \ + barcode2statistics[barcode]['too_long'] sanity_check = barcode2statistics[barcode]['poor_quality_barcode'] + barcode2statistics[barcode]['high_quality_barcode'] == barcode2statistics[barcode]['legal_barcode'] assert sanity_check, 'poor_quality + high_quality != total' @@ -257,7 +280,8 @@ def filter_reads(argv, fastq_file, parsed_fastq_results, logs_dir, f.write(f"Poor quality barcode -> {barcode2statistics[barcode]['poor_quality_barcode']}\n" f"More than {max_mismatches_allowed} mistakes in the flanking constant sequences -> {barcode2statistics[barcode]['too_many_mistakes']}\n" f"Nonsense stop codon -> {barcode2statistics[barcode]['stop_codon']}\n" - f"Too short NNK sequences -> {barcode2statistics[barcode]['too_short']}\n") + f"Too short NNK sequences -> {barcode2statistics[barcode]['too_short']}\n" + f"Too long NNK sequences -> {barcode2statistics[barcode]['too_long']}\n") write_header(f, f"\n\nTOTAL NUMBER OF TRANSLATED SEQUENCES -> {barcode2statistics[barcode]['total_translated_sequences']}\n\n") @@ -270,10 +294,10 @@ def filter_reads(argv, fastq_file, parsed_fastq_results, logs_dir, logger.info('Writing execution summary file...') - with open(f'{parsed_fastq_results}/summary_log.txt', 'w') as log_f: + with open(f'{parsed_fastq_results}/{name_summary_file}', 'w') as log_f: log_f.write(f'Starting {argv[0]}. Executed command is:\n{" ".join(argv)}\n') log_f.write(f'reads_filtration function is invoked with the following parameters:\n') - log_f.write(f'fastq_file = {fastq_file}\n' + log_f.write(f'fastq_path = {fastq_path}\n' f'parsed_fastq_results = {parsed_fastq_results}\n' f'logs_dir = {logs_dir}\n' f'barcode2samplename_path = {barcode2samplename_path}\n' @@ -295,7 +319,8 @@ def filter_reads(argv, fastq_file, parsed_fastq_results, logs_dir, total_filtered_reads = sum(barcode2statistics[barcode]['poor_quality_barcode'] + barcode2statistics[barcode]['too_many_mistakes'] + barcode2statistics[barcode]['stop_codon'] + - barcode2statistics[barcode]['too_short'] for barcode in barcode2samplename) + barcode2statistics[barcode]['too_short']+ + barcode2statistics[barcode]['too_long'] for barcode in barcode2samplename) write_header(log_f, f"Total number of reads that were filtered (due to the following reasons) -> {total_filtered_reads}\n") @@ -303,7 +328,7 @@ def filter_reads(argv, fastq_file, parsed_fastq_results, logs_dir, f"More than {max_mismatches_allowed} mistakes in the flanking constant sequences -> " f"{sum(barcode2statistics[barcode]['too_many_mistakes'] for barcode in barcode2samplename)}\n" f"Nonsense stop codon -> {sum(barcode2statistics[barcode]['stop_codon'] for barcode in barcode2samplename)}\n" - f"Not NNK sequences -> {sum(barcode2statistics[barcode]['too_short'] for barcode in barcode2samplename)}\n") + f"Not NNK sequences -> {sum(barcode2statistics[barcode]['too_short'] + barcode2statistics[barcode]['too_long'] for barcode in barcode2samplename)}\n") total_translated_sequences = sum(barcode2statistics[barcode]['total_translated_sequences'] for barcode in barcode2samplename) write_header(log_f, f'\n\nTOTAL NUMBER OF TRANSLATED SEQUENCES -> {total_translated_sequences}\n\n') @@ -342,6 +367,7 @@ def filter_reads(argv, fastq_file, parsed_fastq_results, logs_dir, parser.add_argument('logs_dir', type=str, help='logs folder') parser.add_argument('done_file_path', help='A path to a file that signals that the script finished running successfully.') parser.add_argument('barcode2samplename', type=str, help='A path to the barcode to sample name file') + parser.add_argument('--name_summary_file', default='summary_log.txt', type=str, help='A name for summary file of filter reads.') parser.add_argument('--error_path', type=str, help='a file in which errors will be written to') parser.add_argument('--left_construct', type=str, default="CAACGTGGC", help='left constant sequence') parser.add_argument('--right_construct', type=str, default="GCCT", help='right constant sequence') @@ -354,6 +380,7 @@ def filter_reads(argv, fastq_file, parsed_fastq_results, logs_dir, # parser.add_argument('--lib_types', type=str.upper, default='6,C6C,8,C8C,10,C10C,12', help='OBSOLETE: Ignore this param. CxC,x') parser.add_argument('--minimal_length_required', default=3, type=int, help='Shorter peptides will be discarded') + parser.add_argument('--maximum_length_required', default=14, type=int, help='Longer peptides will be discarded') parser.add_argument('--gz', action='store_true', help='gzip fastq, filtration_log, fna, and faa files') parser.add_argument('-v', '--verbose', action='store_true', help='Increase output verbosity') args = parser.parse_args() @@ -367,12 +394,7 @@ def filter_reads(argv, fastq_file, parsed_fastq_results, logs_dir, error_path = args.error_path if args.error_path else os.path.join(args.parsed_fastq_results, 'error.txt') - # try: filter_reads(sys.argv, args.fastq_path, args.parsed_fastq_results, args.logs_dir, - args.done_file_path, args.barcode2samplename, + args.done_file_path, args.barcode2samplename, args.name_summary_file, args.left_construct, args.right_construct, args.max_mismatches_allowed, - args.min_sequencing_quality, args.minimal_length_required, - True if args.gz else False) - # except Exception as e: - # fail(error_path, e) - + args.min_sequencing_quality, args.minimal_length_required, args.maximum_length_required, args.gz) diff --git a/reads_filtration/mapitope_conversion.py b/reads_filtration/mapitope_conversion.py new file mode 100644 index 0000000..4ac46f7 --- /dev/null +++ b/reads_filtration/mapitope_conversion.py @@ -0,0 +1,64 @@ +# import datetime +import sys +import logging +import os +logger = logging.getLogger('main') + +mapitope = { + 'R': 'R', + 'K': 'R', + 'E': 'E', + 'D': 'E', + 'S': 'S', + 'T': 'S', + 'I': 'I', + 'L': 'I', + 'V': 'I', + 'Q': 'Q', + 'N': 'Q', + 'Y': 'Y', + 'F': 'Y', + 'A': 'A', + 'C': 'C', + 'G': 'G', + 'H': 'H', + 'M': 'M', + 'P': 'P', + 'W': 'W' +} + +def convert_to_mapitope(sequence): + return ''.join([mapitope[c] for c in sequence]) + + +def convert_faa_file_by_maptope(input_file_path, output_file_path, done_file_path, argv='no_argv'): + with open(input_file_path,'r') as input_file, open(output_file_path,'w+') as output_file: + input_lines = input_file.readlines() + for line in input_lines: + if line[0] == '>': + output_file.write(line) + else: + output_file.write(convert_to_mapitope(line[:-1])+'\n') + + with open(done_file_path, 'w') as f: + f.write(' '.join(argv) + '\n') + +if __name__ == '__main__': + print(f'Starting {sys.argv[0]}. Executed command is:\n{" ".join(sys.argv)}') + import argparse + parser = argparse.ArgumentParser() + parser.add_argument('input_path', help='path to reads_filtration folder') + parser.add_argument('output_path', help='path to barcode2sample file') + parser.add_argument('done_file_path', help='A path to a file that signals that the script finished running successfully.') + parser.add_argument('-v', '--verbose', action='store_true', help='Increase output verbosity') + args = parser.parse_args() + + import logging + if args.verbose: + logging.basicConfig(level=logging.DEBUG) + else: + logging.basicConfig(level=logging.INFO) + logger = logging.getLogger('main') + + convert_faa_file_by_maptope(args.input_path, args.output_path, args.done_file_path,sys.argv) + \ No newline at end of file diff --git a/reads_filtration/module_wraper.py b/reads_filtration/module_wraper.py index a39ccba..4dd1b24 100644 --- a/reads_filtration/module_wraper.py +++ b/reads_filtration/module_wraper.py @@ -1,96 +1,183 @@ import datetime +from operator import ne import os import sys +import json if os.path.exists('/groups/pupko/orenavr2/'): src_dir = '/groups/pupko/orenavr2/igomeProfilingPipeline/src' -else: +elif os.path.exists('/Users/Oren/Dropbox/Projects/'): src_dir = '/Users/Oren/Dropbox/Projects/gershoni/src' +else: + src_dir = '.' sys.path.insert(0, src_dir) -from auxiliaries.pipeline_auxiliaries import fetch_cmd, wait_for_results +from auxiliaries.pipeline_auxiliaries import fetch_cmd, wait_for_results, load_table_to_dict, process_params +from auxiliaries.validation_files import is_input_files_valid +from auxiliaries.stop_machine_aws import stop_machines from global_params import src_dir -def run_first_phase(fastq_path, first_phase_output_path, logs_dir, barcode2samplename, first_phase_done_path, - left_construct, right_construct, max_mismatches_allowed, min_sequencing_quality, - gz, verbose, error_path, queue, argv='no_argv'): +map_names_command_line = { + "fastq_path" : "fastq", + "parsed_fastq_results" : "reads_path", + "logs_dir" : "logs_dir", + "barcode2samplename" : "barcode2sample", + "done_file_path" : "done_file_path", + "left_construct" : "left_construct", + "right_construct" : "right_construct", + "max_mismatches_allowed" : "max_mismatches_allowed", + "min_sequencing_quality" : "min_sequencing_quality", + "minimal_length_required" : "minimal_length_required", + "maximum_length_required" : "maximum_length_required", + "multi_exp_config_reads" : "multi_experiments_config", + "check_files_valid" : "check_files_valid", + "stop_machines" : "stop_machines_flag", + "type_machines_to_stop" : "type_machines_to_stop", + "name_machines_to_stop" : "name_machines_to_stop", + "name_summary_file_reads" : "name_summary_file_reads", + "gz" : "gz", + "verbose" : "verbose", + "error_path" : "error_path", + "queue" : "queue", + "no_calculate_rpm" : "no_calculate_rpm", + "mapitope" : "mapitope" +} + + +def run_first_phase(fastq, reads_path, logs_dir, barcode2sample, done_file_path, + left_construct, right_construct, max_mismatches_allowed, min_sequencing_quality, minimal_length_required, maximum_length_required, + multi_experiments_config, check_files_valid, stop_machines_flag, type_machines_to_stop, name_machines_to_stop, name_summary_file_reads, + no_calculate_rpm, gz, verbose, mapitope, error_path, queue, exp_name, argv): + + if exp_name: + logger.info(f'{datetime.datetime.now()}: Start reads filtration step for experiments {exp_name})') + + # check the validation of files barcode2samplename_path and samplename2biologicalcondition_path + if check_files_valid and not is_input_files_valid(samplename2biologicalcondition_path='', barcode2samplename_path=barcode2sample, logger=logger): + return - os.makedirs(first_phase_output_path, exist_ok=True) + if os.path.exists(done_file_path): + logger.info(f'{datetime.datetime.now()}: skipping reads_filtration step ({done_file_path} already exists)') + return + + os.makedirs(reads_path, exist_ok=True) os.makedirs(logs_dir, exist_ok=True) + + barcode2samplename_dict = load_table_to_dict(barcode2sample, 'Barcode {} belongs to more than one sample_name!!') + sample_names = list(set(sorted(barcode2samplename_dict.values()))) + error_path = error_path or os.path.join(reads_path, 'error.txt') - if os.path.exists(first_phase_done_path): - logger.info(f'{datetime.datetime.now()}: skipping reads_filtration step ({first_phase_done_path} already exists)') - return - - done_path = f'{logs_dir}/done_demultiplexing.txt' + script_name = 'filter_reads.py' + done_path = f'{logs_dir}/{exp_name}_done_demultiplexing.txt' logger.info('_' * 100) - logger.info(f'{datetime.datetime.now()}: demultiplexig sequences for {first_phase_output_path}') + logger.info(f'{datetime.datetime.now()}: demultiplexig sequences for {done_path}') if not os.path.exists(done_path): # run filter_reads.py - parameters = [fastq_path, first_phase_output_path, logs_dir, - done_path, barcode2samplename, + parameters = [fastq, reads_path, logs_dir, + done_path, barcode2sample, f'--error_path {error_path}', f'--left_construct {left_construct}', f'--right_construct {right_construct}', f'--max_mismatches_allowed {max_mismatches_allowed}', - f'--min_sequencing_quality {min_sequencing_quality}'] + (['--gz'] if gz else []) + f'--min_sequencing_quality {min_sequencing_quality}', + f'--minimal_length_required {minimal_length_required}', + f'--maximum_length_required {maximum_length_required}'] + (['--gz'] if gz else []) - fetch_cmd(f'{src_dir}/reads_filtration/filter_reads.py', + fetch_cmd(f'{src_dir}/reads_filtration/{script_name}', parameters, verbose, error_path) num_of_expected_results = 1 - wait_for_results('filter_reads.py', logs_dir, num_of_expected_results, + wait_for_results(script_name, logs_dir, num_of_expected_results, error_file_path=error_path, suffix='demultiplexing.txt') else: - logger.info(f'{datetime.datetime.now()}: skipping filter_reads.py ({done_path} exists)') + logger.info(f'{datetime.datetime.now()}: skipping {script_name} ({done_path} exists)') + + if mapitope: + script_name = 'mapitope_conversion.py' + mapitope_done_path = f'{logs_dir}/01_done_mapitope_encoding.txt' + if not os.path.exists(mapitope_done_path): + logger.info('_' * 100) + logger.info(f'{datetime.datetime.now()}: mapitope encoding data {mapitope_done_path}') + # run mapitope_conversion.py + num_of_expected_results = 0 + for sample_name in sorted(sample_names): + dir_path = os.path.join(reads_path, sample_name) + if not os.path.isdir(dir_path): + continue + done_path = f'{logs_dir}/01_{sample_name}_done_converting_to_mapitope.txt' + parameters = [ + f'{reads_path}/{sample_name}/{sample_name}.faa', + f'{reads_path}/{sample_name}/{sample_name}_mapitope.faa', + done_path + ] + fetch_cmd(f'{src_dir}/reads_filtration/{script_name}', parameters, verbose, error_path, done_path) + num_of_expected_results += 1 + + wait_for_results(script_name, logs_dir, num_of_expected_results, error_file_path=error_path, suffix='mapitope.txt') + + with open(mapitope_done_path, 'w') as f: + f.write(' '.join(argv) + '\n') + else: + logger.info(f'{datetime.datetime.now()}: skipping {script_name} ({mapitope_done_path} exists)') + + script_name = 'count_and_collapse_duplicates.py' collapsing_done_path = f'{logs_dir}/02_done_collapsing_all.txt' if not os.path.exists(collapsing_done_path): logger.info('_' * 100) - logger.info(f'{datetime.datetime.now()}: counting and collapsing duplicated sequences for {first_phase_output_path}') - # run count_and_collapse_duplicates.py and remove_cysteine_loop.py + logger.info(f'{datetime.datetime.now()}: demultiplexig sequences for {collapsing_done_path}') num_of_expected_results = 0 - for dir_name in sorted(os.listdir(first_phase_output_path)): - dir_path = os.path.join(first_phase_output_path, dir_name) + for dir_name in sorted(sample_names): + dir_path = os.path.join(reads_path, dir_name) if not os.path.isdir(dir_path): continue for file in os.listdir(dir_path): # look for faa files to collapse - if not file.startswith(f'{dir_name}.faa'): # maybe there's a .gz afterwards + if not file.startswith(f'{dir_name}.faa') and not file.startswith(f'{dir_name}_mapitope.faa'): # maybe there's a .gz afterwards continue sample_name = file.split('.faa')[0] file_path = f'{dir_path}/{file}' output_file_path = f'{dir_path}/{sample_name}_unique_rpm.faa' done_path = f'{logs_dir}/02_{sample_name}_done_collapsing.txt' + factors_file_name = 'mapitop_rpm_factors' if 'mapitope' in file else 'rpm_factors' + parameters = [file_path, output_file_path, done_path] + if not no_calculate_rpm: + rpm_factors_path = f'{reads_path}/{factors_file_name}.txt' + parameters.append('--rpm') + parameters.append(rpm_factors_path) + if not os.path.exists(done_path): + fetch_cmd(f'{src_dir}/reads_filtration/{script_name}', parameters, verbose, error_path, done_path) + num_of_expected_results += 1 + else: + logger.debug(f'skipping filter reads as {done_path} found') + num_of_expected_results += 1 + + + wait_for_results(script_name, logs_dir, num_of_expected_results, + error_file_path=error_path, suffix='collapsing.txt') + else: + logger.info(f'{datetime.datetime.now()}: skipping {script_name} ({collapsing_done_path} exists)') - parameters = [file_path, output_file_path, done_path, - '--rpm', f'{first_phase_output_path}/rpm_factors.txt'] - fetch_cmd(f'{src_dir}/reads_filtration/count_and_collapse_duplicates.py', - parameters, verbose, error_path) - - # file_path = output_file_path - # output_file_path = f'{os.path.splitext(file_path)[0]}_cysteine_trimmed.faa' - # parameters = [file_path, output_file_path] - # fetch_cmd(f'{src_dir}/reads_filtration/remove_cysteine_loop.py', - # parameters, verbose, error_path) - # TODO: if remove_cysteine_loop.py is fetched, the counts should be recalculated! - # E.g.: CAAAAC and AAAA are the same after removing Cys - - num_of_expected_results += 1 - break - - wait_for_results('count_and_collapse_duplicates.py', logs_dir, num_of_expected_results, - error_file_path=error_path, suffix='collapsing.txt') - with open(collapsing_done_path, 'w') as f: - f.write(' '.join(argv) + '\n') - - + script_name = 'summary_reads.py' + done_path = f'{logs_dir}/03_done_summary_reads.txt' + if not os.path.exists(done_path): + logger.info('_' * 100) + logger.info(f'{datetime.datetime.now()}: summary reads for {done_path}') + parameters = [barcode2sample, reads_path, done_path, f'--name_summary_file_reads {name_summary_file_reads}', + '--no_calculate_rpm' if no_calculate_rpm else ''] + fetch_cmd(f'{src_dir}/reads_filtration/{script_name}', parameters, verbose, error_path) + num_of_expected_results = 1 + wait_for_results(script_name, logs_dir, num_of_expected_results, + error_file_path=error_path, suffix='summary_reads.txt') else: - logger.info(f'{datetime.datetime.now()}: skipping count_and_collapse_duplicates.py ({done_path} exists)') + logger.info(f'{datetime.datetime.now()}: skipping {script_name} ({done_path} exists)') - with open(first_phase_done_path, 'w') as f: + with open(done_file_path, 'w') as f: f.write(' '.join(argv) + '\n') + if stop_machines_flag: + stop_machines(type_machines_to_stop, name_machines_to_stop, logger) + if __name__ == '__main__': print(f'Starting {sys.argv[0]}. Executed command is:\n{" ".join(sys.argv)}', flush=True) @@ -108,12 +195,22 @@ def run_first_phase(fastq_path, first_phase_output_path, logs_dir, barcode2sampl parser.add_argument('min_sequencing_quality', type=int, default=38, help='Minimum average sequencing threshold allowed after filtration' 'for more details, see: https://en.wikipedia.org/wiki/Phred_quality_score') - parser.add_argument('done_file_path', help='A path to a file that signals that the module finished running successfully.') - + parser.add_argument('done_file_path', help='A path to a file that signals that the module finished running successfully') + parser.add_argument('minimal_length_required', default=3, type=int, help='Shorter peptides will be discarded') + + parser.add_argument('--maximum_length_required', default=14, type=int, help='Longer peptides will be discarded') + parser.add_argument('--multi_exp_config_reads', type=str, help='Configuration file for reads phase to run multi expirements') + parser.add_argument('--check_files_valid', action='store_true', help='Need to check the validation of the files (samplename2biologicalcondition_path / barcode2samplenaem)') + parser.add_argument('--stop_machines', action='store_true', help='Turn off the machines in AWS at the end of the running') + parser.add_argument('--type_machines_to_stop', default='', type=str, help='Type of machines to stop, separated by comma. Empty value means all machines. Example: t2.2xlarge,m5a.24xlarge') + parser.add_argument('--name_machines_to_stop', default='', type=str, help='Names (patterns) of machines to stop, separated by comma. Empty value means all machines. Example: worker*') + parser.add_argument('--no_calculate_rpm', action='store_true', help='Disable normalize counts to "reads per million" (sequence proportion x 1,000,000)') + parser.add_argument('--name_summary_file_reads', default='summary_log_reads.csv', type=str, help='A name for summary file summary all reads.') parser.add_argument('--error_path', type=str, help='a file in which errors will be written to') parser.add_argument('--gz', action='store_true', help='gzip fastq, filtration_log, fna, and faa files') parser.add_argument('-q', '--queue', default='pupkoweb', type=str, help='a queue to which the jobs will be submitted') parser.add_argument('-v', '--verbose', action='store_true', help='Increase output verbosity') + parser.add_argument('-m', '--mapitope', action='store_true', help='use mapitope encoding') args = parser.parse_args() import logging @@ -122,11 +219,5 @@ def run_first_phase(fastq_path, first_phase_output_path, logs_dir, barcode2sampl else: logging.basicConfig(level=logging.INFO) logger = logging.getLogger('main') - - error_path = args.error_path if args.error_path else os.path.join(args.parsed_fastq_results, 'error.txt') - - run_first_phase(args.fastq_path, args.parsed_fastq_results, args.logs_dir, - args.barcode2samplename, args.done_file_path, args.left_construct, - args.right_construct, args.max_mismatches_allowed, - args.min_sequencing_quality, True if args.gz else False, - True if args.verbose else False, error_path, args.queue, sys.argv) + + process_params(args, args.multi_exp_config_reads, map_names_command_line, run_first_phase, 'reads_filtration',sys.argv) diff --git a/reads_filtration/summary_reads.py b/reads_filtration/summary_reads.py new file mode 100644 index 0000000..ce32b43 --- /dev/null +++ b/reads_filtration/summary_reads.py @@ -0,0 +1,110 @@ +import sys +import csv +import os +import math +if os.path.exists('/groups/pupko/orenavr2/'): + src_dir = '/groups/pupko/orenavr2/igomeProfilingPipeline/src' +elif os.path.exists('/Users/Oren/Dropbox/Projects/'): + src_dir = '/Users/Oren/Dropbox/Projects/gershoni/src' +else: + src_dir = '.' +sys.path.insert(0, src_dir) + +from auxiliaries.pipeline_auxiliaries import load_table_to_dict + + +def get_rpm_factor(parsed_fastq_results, sample_name_of_barcode, delimiter='\t'): + path_rpm_factor = f'{parsed_fastq_results}/rpm_factors.txt' + file_rpm_factor = open(path_rpm_factor, 'r') + for line in file_rpm_factor.readlines(): + if line.isspace(): # empty line + continue + sample_name, count_peptides, rpm = line.strip().split(delimiter) + if sample_name == sample_name_of_barcode: + return str(rpm) + + +def get_number_unique_peptides(parsed_fastq_results, sample_name): + path_unique_rpm_file = f'{parsed_fastq_results}/{sample_name}/{sample_name}_unique_rpm.faa' + num_unique_peptides = str(math.ceil(len(open(path_unique_rpm_file, 'r').readlines()) / 2)) + return num_unique_peptides + + +def get_num_peptides_to_barcode(lines, barcode): + for line in lines: + if line.startswith(barcode): + return line.split()[-1] + + +def get_count_peptides_before_and_after_filtration(parsed_fastq_results, barcode, num_barcodes): + path_summary_log_txt = f'{parsed_fastq_results}/summary_log.txt' + file_summary_log_txt = open(path_summary_log_txt, 'r') + lines = file_summary_log_txt.readlines() + before_filtration = '' + after_filtration = '' + for ind,line in enumerate(lines): + if line.startswith('Total number of dna sequences with a LEGAL barcode'): + before_filtration = get_num_peptides_to_barcode(lines[ind + 1 : ind + 1 + num_barcodes], barcode) + elif line.startswith('Translated peptides (per barcode)'): + after_filtration = get_num_peptides_to_barcode(lines[ind + 1 : ind + 1 + num_barcodes], barcode) + break + else: + continue + return [before_filtration, after_filtration] + + +def summary_reads(barcode2samplename, parsed_fastq_results, done_file_path, name_summary_file_reads, no_calculate_rpm, argv): + barcode2samplename_dict = load_table_to_dict(barcode2samplename, 'Barcode {} belongs to more than one sample_name!!') + + header = ["barcode", "sample name", "Before filtration per barcode", "After filtration per barcode", "Unique peptides per sample", "1 RPM per sample"] + with open(f'{parsed_fastq_results}/{name_summary_file_reads}', 'w') as f: + writer = csv.writer(f) + # write the header + writer.writerow(header) + # write the data + for barcode in barcode2samplename_dict: + data = [] + sample_name = barcode2samplename_dict[barcode] + # Bracode + data.append(barcode) + + # Sample name + data.append(sample_name) + + # Before filtration and After filtration + data = data + get_count_peptides_before_and_after_filtration(parsed_fastq_results, barcode, len(barcode2samplename_dict)) + + # Unique peptides + data.append(get_number_unique_peptides(parsed_fastq_results, sample_name)) + + # 1 RPM + rpm_factor = '' if no_calculate_rpm else get_rpm_factor(parsed_fastq_results, sample_name) + data.append(rpm_factor) + + writer.writerow(data) + + with open(done_file_path, 'w') as f: + f.write(' '.join(argv) + '\n') + + +if __name__ == '__main__': + print(f'Starting {sys.argv[0]}. Executed command is:\n{" ".join(sys.argv)}', flush=True) + import argparse + parser = argparse.ArgumentParser() + parser.add_argument('barcode2samplename', type=str, help='A path to the barcode to sample name file') + parser.add_argument('parsed_fastq_results', type=str, help='output folder') + parser.add_argument('done_file_path', type=str, help='A path to a file that signals that the summary reads finished running successfully') + parser.add_argument('--name_summary_file_reads', default='summary_log_reads.csv', type=str, help='A name for summary file summary all reads.') + parser.add_argument('--no_calculate_rpm', action='store_true', help='Disable normalize counts to "reads per million" (sequence proportion x 1,000,000)') + parser.add_argument('-v', '--verbose', action='store_true', help='Increase output verbosity') + + args = parser.parse_args() + + import logging + if args.verbose: + logging.basicConfig(level=logging.DEBUG) + else: + logging.basicConfig(level=logging.INFO) + logger = logging.getLogger('main') + + summary_reads(args.barcode2samplename, args.parsed_fastq_results, args.done_file_path, args.name_summary_file_reads, args.no_calculate_rpm, sys.argv) diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..c87ab5c --- /dev/null +++ b/requirements.txt @@ -0,0 +1,6 @@ +seaborn == 0.10.0 +scikit-learn == 0.22.1 +weblogo == 3.7.1 +celery[librabbitmq,eventlet] == 4.4.0 +jsonschema == 3.2.0 +boto3 == 1.17.58 diff --git a/run_mock.sh b/run_mock.sh new file mode 100644 index 0000000..29980ef --- /dev/null +++ b/run_mock.sh @@ -0,0 +1,10 @@ +#!/bin/bash +rm -rf output +mkdir output && mkdir output/analysis && mkdir output/logs +source .venv/bin/activate +python3 IgOmeProfiling_pipeline.py \ + mock_data/exp12_10M_rows.fastq.gz \ + mock_data/barcode2samplename.txt \ + mock_data/samplename2biologicalcondition.txt \ + output/analysis \ + output/logs diff --git a/run_rabbit.sh b/run_rabbit.sh new file mode 100644 index 0000000..d128058 --- /dev/null +++ b/run_rabbit.sh @@ -0,0 +1,3 @@ +#!/bin/bash +docker-compose -f rabbit-docker-compose.yml down +docker-compose -f rabbit-docker-compose.yml up -d diff --git a/shuffles_generator.py b/shuffles_generator.py new file mode 100644 index 0000000..ff40b62 --- /dev/null +++ b/shuffles_generator.py @@ -0,0 +1,164 @@ +from random import shuffle, choice, seed, random + +RANDOM_SEED = 1234 +DISTANCE_FACTOR = 1 +STAY_AT_SAME_PLACE_PENALTY = -1 +MIN_NEIGHBORS = 1 +NEIGHBORS_PENALTY = -1 +NEIGHBORS_POSITIVE = 0 +SET_POSITION_MOVE_PROB = 1 +MAX_STEPS_FACTOR = 10 +CUTOFF_SCORE_FACTOR = 1 +CUTOFF_SCORE_METHOD = 'subtract' # subtract or multiply + +def create_board(length): + unset = [] + positions = {} + for i in range(length): + unset.append(i) + positions[i] = i + return unset, positions + + +def calculate_score(positions): + score = 0 + for i in positions: + if positions[i] == i: + score += STAY_AT_SAME_PLACE_PENALTY + else: + score += abs(i - positions[i]) * DISTANCE_FACTOR + if (i+1) in positions: + if positions[i+1] > positions[i] and \ + positions[i+1] - positions[i] <= MIN_NEIGHBORS: + score += NEIGHBORS_PENALTY + else: + score += NEIGHBORS_POSITIVE + return score + + +def is_set_move(unset): + if len(unset) == 0: + return False + if SET_POSITION_MOVE_PROB == 1: + return True + return random() < SET_POSITION_MOVE_PROB + + +def listToDict(list): + result = {} + for i in range(len(list)): + result[i] = list[i] + return result + + +def calculate_cutoff_score(length): + score = 0 + pos = length - 1 + for _ in range(length - 1, length // 2 - 1, -1): + score += pos + pos -= 2 + score *= 2 + if CUTOFF_SCORE_METHOD == 'multiply': + cutoff = score * CUTOFF_SCORE_FACTOR + else: + cutoff = score - CUTOFF_SCORE_FACTOR + return score, cutoff + + +def swap(positions, index, new_index): + a = positions[index] + b = positions[new_index] + positions[index] = b + positions[new_index] = a + + +def get_best_swaps(index, positions): + scores = {} + length = len(positions) + for i in range(length): + new_positions = positions.copy() + swap(new_positions, index, i) + score = calculate_score(new_positions) + try: + scores[score].append(new_positions) + except: + scores[score] = [new_positions] + max_score = max(scores.keys()) + return scores[max_score] + + +def play(unset, positions, cutoff): + length = len(unset) + indexes = [i for i in range(length)] + for _ in range(length * MAX_STEPS_FACTOR): + if is_set_move(unset): + index = choice(unset) + unset.remove(index) + else: + index = choice(indexes) + best_positions = get_best_swaps(index, positions) + positions = choice(best_positions) + score = calculate_score(positions) + if score >= cutoff: + break + + + score = calculate_score(positions) + return positions, score + + +def max_shuffle(length, iterations=1, file=None, output_hpp=False): + unset, positions = create_board(length) + # print(calculate_score(positions)) + seed(RANDOM_SEED) + max_score, cutoff = calculate_cutoff_score(length) + + if file: + def writeLine(*args): + if output_hpp: + if len(args) == 1: + file.write(f'{args[0]}\n') + else: + file.write(f'\t\t\t{{{",".join([str(x) for x in args[0]])}}},\n') + else: + file.write(' '.join([str(arg) for arg in args])) + file.write('\n') + print_func = writeLine + else: + print_func = print + + results = {} + for _ in range(iterations): + iteration_positions, iteration_score = play(list(unset), list(positions), cutoff) + results[tuple(iteration_positions)] = iteration_score + if output_hpp: + print_func(f'\t{{\t{length}, {{') + else: + print_func(f'length={length}, max_score={max_score}, cutoff={cutoff}') + for result in results: + print_func(result, results[result]) + if output_hpp: + print_func(f'\t\t}}\n\t}},') + + +if __name__ == '__main__': + min_length = 1 + max_length = 20 + iterations = 100 + output_hpp = True + output_ext = 'hpp' if output_hpp else 'txt' + + with open(f'controlled_shuffles.{output_ext}', 'w') as f: + if output_hpp: + f.write('#include \n') + f.write('#include \n') + f.write('using namespace std;\n') + f.write('\n') + f.write('typedef vector ShufflePattern;\n') + f.write('typedef vector ShufflePatterns;\n') + f.write('typedef map ShufflesMap;\n') + f.write('ShufflesMap _shuffle_sequences = {\n') + for length in range(min_length, max_length + 1): + max_shuffle(length, iterations, f, output_hpp) + if output_hpp: + f.write('};\n') diff --git a/tfidf/calculateScores.cpp b/tfidf/calculateScores.cpp new file mode 100644 index 0000000..fcc85b2 --- /dev/null +++ b/tfidf/calculateScores.cpp @@ -0,0 +1,53 @@ +#include +#include "types.hpp" +#include "scans.hpp" +#include "meme.hpp" +#include "memeSample.hpp" + +void calculateScores(Scans& scans, TFMethod eMethod, float augmentFactor, bool isUseAllSequences) { + auto memes = &scans.getMemes(); + auto sequencesCount = &scans.getSequencesCount(); + + auto memesIter = memes->begin(); + auto memesEnd = memes->end(); + auto bcSequencesEnd = scans.getBCSequences().end(); + double memesCount = (double)memes->size(); + // cout << "memes count: " << memesCount << endl; + while (memesIter != memesEnd) { + auto samplesIter = memesIter->second->getSamples().begin(); + auto samplesEnd = memesIter->second->getSamples().end(); + while (samplesIter != samplesEnd) { + auto sequencesIter = samplesIter->second->getSequences().begin(); + auto sequencesEnd = samplesIter->second->getSequences().end(); + double score = 0; + while (sequencesIter != sequencesEnd) { + if (isUseAllSequences || scans.getBCSequences().find(sequencesIter->first) != bcSequencesEnd) { + // TODO calculate cosine similarity with all relevant mems if on + auto sequenceMemesCount = sequencesCount->find(sequencesIter->first)->second; + double idf = log(memesCount / sequenceMemesCount); + double tf = 0; + switch (eMethod) { + case TFMethod_BOOL: + tf = 1; + break; + case TFMethod_TERMS: + tf = (double)sequencesIter->second / samplesIter->second->getHitsCount(); + break; + case TFMethod_LOG: + tf = log(1 + sequencesIter->second); + break; + case TFMethod_AUGMENT: + tf = augmentFactor + (1 - augmentFactor) * + ((double)sequencesIter->second / samplesIter->second->getMaxSequenceCount()); + break; + } + score += tf * idf; + } + sequencesIter++; + } + samplesIter->second->setScore(score); + samplesIter++; + } + memesIter++; + } +} diff --git a/tfidf/cxxopts.hpp b/tfidf/cxxopts.hpp new file mode 100644 index 0000000..95434a7 --- /dev/null +++ b/tfidf/cxxopts.hpp @@ -0,0 +1,2104 @@ +/* + +Copyright (c) 2014, 2015, 2016, 2017 Jarryd Beck + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + +*/ + +#ifndef CXXOPTS_HPP_INCLUDED +#define CXXOPTS_HPP_INCLUDED + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef __cpp_lib_optional +#include +#define CXXOPTS_HAS_OPTIONAL +#endif + +#ifndef CXXOPTS_VECTOR_DELIMITER +#define CXXOPTS_VECTOR_DELIMITER ',' +#endif + +#define CXXOPTS__VERSION_MAJOR 2 +#define CXXOPTS__VERSION_MINOR 2 +#define CXXOPTS__VERSION_PATCH 0 + +namespace cxxopts +{ + static constexpr struct { + uint8_t major, minor, patch; + } version = { + CXXOPTS__VERSION_MAJOR, + CXXOPTS__VERSION_MINOR, + CXXOPTS__VERSION_PATCH + }; +} + +//when we ask cxxopts to use Unicode, help strings are processed using ICU, +//which results in the correct lengths being computed for strings when they +//are formatted for the help output +//it is necessary to make sure that can be found by the +//compiler, and that icu-uc is linked in to the binary. + +#ifdef CXXOPTS_USE_UNICODE +#include + +namespace cxxopts +{ + typedef icu::UnicodeString String; + + inline + String + toLocalString(std::string s) + { + return icu::UnicodeString::fromUTF8(std::move(s)); + } + + class UnicodeStringIterator : public + std::iterator + { + public: + + UnicodeStringIterator(const icu::UnicodeString* string, int32_t pos) + : s(string) + , i(pos) + { + } + + value_type + operator*() const + { + return s->char32At(i); + } + + bool + operator==(const UnicodeStringIterator& rhs) const + { + return s == rhs.s && i == rhs.i; + } + + bool + operator!=(const UnicodeStringIterator& rhs) const + { + return !(*this == rhs); + } + + UnicodeStringIterator& + operator++() + { + ++i; + return *this; + } + + UnicodeStringIterator + operator+(int32_t v) + { + return UnicodeStringIterator(s, i + v); + } + + private: + const icu::UnicodeString* s; + int32_t i; + }; + + inline + String& + stringAppend(String&s, String a) + { + return s.append(std::move(a)); + } + + inline + String& + stringAppend(String& s, int n, UChar32 c) + { + for (int i = 0; i != n; ++i) + { + s.append(c); + } + + return s; + } + + template + String& + stringAppend(String& s, Iterator begin, Iterator end) + { + while (begin != end) + { + s.append(*begin); + ++begin; + } + + return s; + } + + inline + size_t + stringLength(const String& s) + { + return s.length(); + } + + inline + std::string + toUTF8String(const String& s) + { + std::string result; + s.toUTF8String(result); + + return result; + } + + inline + bool + empty(const String& s) + { + return s.isEmpty(); + } +} + +namespace std +{ + inline + cxxopts::UnicodeStringIterator + begin(const icu::UnicodeString& s) + { + return cxxopts::UnicodeStringIterator(&s, 0); + } + + inline + cxxopts::UnicodeStringIterator + end(const icu::UnicodeString& s) + { + return cxxopts::UnicodeStringIterator(&s, s.length()); + } +} + +//ifdef CXXOPTS_USE_UNICODE +#else + +namespace cxxopts +{ + typedef std::string String; + + template + T + toLocalString(T&& t) + { + return std::forward(t); + } + + inline + size_t + stringLength(const String& s) + { + return s.length(); + } + + inline + String& + stringAppend(String&s, String a) + { + return s.append(std::move(a)); + } + + inline + String& + stringAppend(String& s, size_t n, char c) + { + return s.append(n, c); + } + + template + String& + stringAppend(String& s, Iterator begin, Iterator end) + { + return s.append(begin, end); + } + + template + std::string + toUTF8String(T&& t) + { + return std::forward(t); + } + + inline + bool + empty(const std::string& s) + { + return s.empty(); + } +} + +//ifdef CXXOPTS_USE_UNICODE +#endif + +namespace cxxopts +{ + namespace + { +#ifdef _WIN32 + const std::string LQUOTE("\'"); + const std::string RQUOTE("\'"); +#else + const std::string LQUOTE("‘"); + const std::string RQUOTE("’"); +#endif + } + + class Value : public std::enable_shared_from_this + { + public: + + virtual ~Value() = default; + + virtual + std::shared_ptr + clone() const = 0; + + virtual void + parse(const std::string& text) const = 0; + + virtual void + parse() const = 0; + + virtual bool + has_default() const = 0; + + virtual bool + is_container() const = 0; + + virtual bool + has_implicit() const = 0; + + virtual std::string + get_default_value() const = 0; + + virtual std::string + get_implicit_value() const = 0; + + virtual std::shared_ptr + default_value(const std::string& value) = 0; + + virtual std::shared_ptr + implicit_value(const std::string& value) = 0; + + virtual std::shared_ptr + no_implicit_value() = 0; + + virtual bool + is_boolean() const = 0; + }; + + class OptionException : public std::exception + { + public: + OptionException(const std::string& message) + : m_message(message) + { + } + + virtual const char* + what() const noexcept + { + return m_message.c_str(); + } + + private: + std::string m_message; + }; + + class OptionSpecException : public OptionException + { + public: + + OptionSpecException(const std::string& message) + : OptionException(message) + { + } + }; + + class OptionParseException : public OptionException + { + public: + OptionParseException(const std::string& message) + : OptionException(message) + { + } + }; + + class option_exists_error : public OptionSpecException + { + public: + option_exists_error(const std::string& option) + : OptionSpecException("Option " + LQUOTE + option + RQUOTE + " already exists") + { + } + }; + + class invalid_option_format_error : public OptionSpecException + { + public: + invalid_option_format_error(const std::string& format) + : OptionSpecException("Invalid option format " + LQUOTE + format + RQUOTE) + { + } + }; + + class option_syntax_exception : public OptionParseException { + public: + option_syntax_exception(const std::string& text) + : OptionParseException("Argument " + LQUOTE + text + RQUOTE + + " starts with a - but has incorrect syntax") + { + } + }; + + class option_not_exists_exception : public OptionParseException + { + public: + option_not_exists_exception(const std::string& option) + : OptionParseException("Option " + LQUOTE + option + RQUOTE + " does not exist") + { + } + }; + + class missing_argument_exception : public OptionParseException + { + public: + missing_argument_exception(const std::string& option) + : OptionParseException( + "Option " + LQUOTE + option + RQUOTE + " is missing an argument" + ) + { + } + }; + + class option_requires_argument_exception : public OptionParseException + { + public: + option_requires_argument_exception(const std::string& option) + : OptionParseException( + "Option " + LQUOTE + option + RQUOTE + " requires an argument" + ) + { + } + }; + + class option_not_has_argument_exception : public OptionParseException + { + public: + option_not_has_argument_exception + ( + const std::string& option, + const std::string& arg + ) + : OptionParseException( + "Option " + LQUOTE + option + RQUOTE + + " does not take an argument, but argument " + + LQUOTE + arg + RQUOTE + " given" + ) + { + } + }; + + class option_not_present_exception : public OptionParseException + { + public: + option_not_present_exception(const std::string& option) + : OptionParseException("Option " + LQUOTE + option + RQUOTE + " not present") + { + } + }; + + class argument_incorrect_type : public OptionParseException + { + public: + argument_incorrect_type + ( + const std::string& arg + ) + : OptionParseException( + "Argument " + LQUOTE + arg + RQUOTE + " failed to parse" + ) + { + } + }; + + class option_required_exception : public OptionParseException + { + public: + option_required_exception(const std::string& option) + : OptionParseException( + "Option " + LQUOTE + option + RQUOTE + " is required but not present" + ) + { + } + }; + + namespace values + { + namespace + { + std::basic_regex integer_pattern + ("(-)?(0x)?([0-9a-zA-Z]+)|((0x)?0)"); + std::basic_regex truthy_pattern + ("(t|T)(rue)?|1"); + std::basic_regex falsy_pattern + ("(f|F)(alse)?|0"); + } + + namespace detail + { + template + struct SignedCheck; + + template + struct SignedCheck + { + template + void + operator()(bool negative, U u, const std::string& text) + { + if (negative) + { + if (u > static_cast((std::numeric_limits::min)())) + { + throw argument_incorrect_type(text); + } + } + else + { + if (u > static_cast((std::numeric_limits::max)())) + { + throw argument_incorrect_type(text); + } + } + } + }; + + template + struct SignedCheck + { + template + void + operator()(bool, U, const std::string&) {} + }; + + template + void + check_signed_range(bool negative, U value, const std::string& text) + { + SignedCheck::is_signed>()(negative, value, text); + } + } + + template + R + checked_negate(T&& t, const std::string&, std::true_type) + { + // if we got to here, then `t` is a positive number that fits into + // `R`. So to avoid MSVC C4146, we first cast it to `R`. + // See https://github.com/jarro2783/cxxopts/issues/62 for more details. + return -static_cast(t-1)-1; + } + + template + T + checked_negate(T&&, const std::string& text, std::false_type) + { + throw argument_incorrect_type(text); + } + + template + void + integer_parser(const std::string& text, T& value) + { + std::smatch match; + std::regex_match(text, match, integer_pattern); + + if (match.length() == 0) + { + throw argument_incorrect_type(text); + } + + if (match.length(4) > 0) + { + value = 0; + return; + } + + using US = typename std::make_unsigned::type; + + constexpr bool is_signed = std::numeric_limits::is_signed; + const bool negative = match.length(1) > 0; + const uint8_t base = match.length(2) > 0 ? 16 : 10; + + auto value_match = match[3]; + + US result = 0; + + for (auto iter = value_match.first; iter != value_match.second; ++iter) + { + US digit = 0; + + if (*iter >= '0' && *iter <= '9') + { + digit = static_cast(*iter - '0'); + } + else if (base == 16 && *iter >= 'a' && *iter <= 'f') + { + digit = static_cast(*iter - 'a' + 10); + } + else if (base == 16 && *iter >= 'A' && *iter <= 'F') + { + digit = static_cast(*iter - 'A' + 10); + } + else + { + throw argument_incorrect_type(text); + } + + US next = result * base + digit; + if (result > next) + { + throw argument_incorrect_type(text); + } + + result = next; + } + + detail::check_signed_range(negative, result, text); + + if (negative) + { + value = checked_negate(result, + text, + std::integral_constant()); + } + else + { + value = static_cast(result); + } + } + + template + void stringstream_parser(const std::string& text, T& value) + { + std::stringstream in(text); + in >> value; + if (!in) { + throw argument_incorrect_type(text); + } + } + + inline + void + parse_value(const std::string& text, uint8_t& value) + { + integer_parser(text, value); + } + + inline + void + parse_value(const std::string& text, int8_t& value) + { + integer_parser(text, value); + } + + inline + void + parse_value(const std::string& text, uint16_t& value) + { + integer_parser(text, value); + } + + inline + void + parse_value(const std::string& text, int16_t& value) + { + integer_parser(text, value); + } + + inline + void + parse_value(const std::string& text, uint32_t& value) + { + integer_parser(text, value); + } + + inline + void + parse_value(const std::string& text, int32_t& value) + { + integer_parser(text, value); + } + + inline + void + parse_value(const std::string& text, uint64_t& value) + { + integer_parser(text, value); + } + + inline + void + parse_value(const std::string& text, int64_t& value) + { + integer_parser(text, value); + } + + inline + void + parse_value(const std::string& text, bool& value) + { + std::smatch result; + std::regex_match(text, result, truthy_pattern); + + if (!result.empty()) + { + value = true; + return; + } + + std::regex_match(text, result, falsy_pattern); + if (!result.empty()) + { + value = false; + return; + } + + throw argument_incorrect_type(text); + } + + inline + void + parse_value(const std::string& text, std::string& value) + { + value = text; + } + + // The fallback parser. It uses the stringstream parser to parse all types + // that have not been overloaded explicitly. It has to be placed in the + // source code before all other more specialized templates. + template + void + parse_value(const std::string& text, T& value) { + stringstream_parser(text, value); + } + + template + void + parse_value(const std::string& text, std::vector& value) + { + std::stringstream in(text); + std::string token; + while(in.eof() == false && std::getline(in, token, CXXOPTS_VECTOR_DELIMITER)) { + T v; + parse_value(token, v); + value.emplace_back(std::move(v)); + } + } + +#ifdef CXXOPTS_HAS_OPTIONAL + template + void + parse_value(const std::string& text, std::optional& value) + { + T result; + parse_value(text, result); + value = std::move(result); + } +#endif + + template + struct type_is_container + { + static constexpr bool value = false; + }; + + template + struct type_is_container> + { + static constexpr bool value = true; + }; + + template + class abstract_value : public Value + { + using Self = abstract_value; + + public: + abstract_value() + : m_result(std::make_shared()) + , m_store(m_result.get()) + { + } + + abstract_value(T* t) + : m_store(t) + { + } + + virtual ~abstract_value() = default; + + abstract_value(const abstract_value& rhs) + { + if (rhs.m_result) + { + m_result = std::make_shared(); + m_store = m_result.get(); + } + else + { + m_store = rhs.m_store; + } + + m_default = rhs.m_default; + m_implicit = rhs.m_implicit; + m_default_value = rhs.m_default_value; + m_implicit_value = rhs.m_implicit_value; + } + + void + parse(const std::string& text) const + { + parse_value(text, *m_store); + } + + bool + is_container() const + { + return type_is_container::value; + } + + void + parse() const + { + parse_value(m_default_value, *m_store); + } + + bool + has_default() const + { + return m_default; + } + + bool + has_implicit() const + { + return m_implicit; + } + + std::shared_ptr + default_value(const std::string& value) + { + m_default = true; + m_default_value = value; + return shared_from_this(); + } + + std::shared_ptr + implicit_value(const std::string& value) + { + m_implicit = true; + m_implicit_value = value; + return shared_from_this(); + } + + std::shared_ptr + no_implicit_value() + { + m_implicit = false; + return shared_from_this(); + } + + std::string + get_default_value() const + { + return m_default_value; + } + + std::string + get_implicit_value() const + { + return m_implicit_value; + } + + bool + is_boolean() const + { + return std::is_same::value; + } + + const T& + get() const + { + if (m_store == nullptr) + { + return *m_result; + } + else + { + return *m_store; + } + } + + protected: + std::shared_ptr m_result; + T* m_store; + + bool m_default = false; + bool m_implicit = false; + + std::string m_default_value; + std::string m_implicit_value; + }; + + template + class standard_value : public abstract_value + { + public: + using abstract_value::abstract_value; + + std::shared_ptr + clone() const + { + return std::make_shared>(*this); + } + }; + + template <> + class standard_value : public abstract_value + { + public: + ~standard_value() = default; + + standard_value() + { + set_default_and_implicit(); + } + + standard_value(bool* b) + : abstract_value(b) + { + set_default_and_implicit(); + } + + std::shared_ptr + clone() const + { + return std::make_shared>(*this); + } + + private: + + void + set_default_and_implicit() + { + m_default = true; + m_default_value = "false"; + m_implicit = true; + m_implicit_value = "true"; + } + }; + } + + template + std::shared_ptr + value() + { + return std::make_shared>(); + } + + template + std::shared_ptr + value(T& t) + { + return std::make_shared>(&t); + } + + class OptionAdder; + + class OptionDetails + { + public: + OptionDetails + ( + const std::string& short_, + const std::string& long_, + const String& desc, + std::shared_ptr val + ) + : m_short(short_) + , m_long(long_) + , m_desc(desc) + , m_value(val) + , m_count(0) + { + } + + OptionDetails(const OptionDetails& rhs) + : m_desc(rhs.m_desc) + , m_count(rhs.m_count) + { + m_value = rhs.m_value->clone(); + } + + OptionDetails(OptionDetails&& rhs) = default; + + const String& + description() const + { + return m_desc; + } + + const Value& value() const { + return *m_value; + } + + std::shared_ptr + make_storage() const + { + return m_value->clone(); + } + + const std::string& + short_name() const + { + return m_short; + } + + const std::string& + long_name() const + { + return m_long; + } + + private: + std::string m_short; + std::string m_long; + String m_desc; + std::shared_ptr m_value; + int m_count; + }; + + struct HelpOptionDetails + { + std::string s; + std::string l; + String desc; + bool has_default; + std::string default_value; + bool has_implicit; + std::string implicit_value; + std::string arg_help; + bool is_container; + bool is_boolean; + }; + + struct HelpGroupDetails + { + std::string name; + std::string description; + std::vector options; + }; + + class OptionValue + { + public: + void + parse + ( + std::shared_ptr details, + const std::string& text + ) + { + ensure_value(details); + ++m_count; + m_value->parse(text); + } + + void + parse_default(std::shared_ptr details) + { + ensure_value(details); + m_value->parse(); + } + + size_t + count() const + { + return m_count; + } + + template + const T& + as() const + { + if (m_value == nullptr) { + throw std::domain_error("No value"); + } + +#ifdef CXXOPTS_NO_RTTI + return static_cast&>(*m_value).get(); +#else + return dynamic_cast&>(*m_value).get(); +#endif + } + + private: + void + ensure_value(std::shared_ptr details) + { + if (m_value == nullptr) + { + m_value = details->make_storage(); + } + } + + std::shared_ptr m_value; + size_t m_count = 0; + }; + + class KeyValue + { + public: + KeyValue(std::string key_, std::string value_) + : m_key(std::move(key_)) + , m_value(std::move(value_)) + { + } + + const + std::string& + key() const + { + return m_key; + } + + const + std::string& + value() const + { + return m_value; + } + + template + T + as() const + { + T result; + values::parse_value(m_value, result); + return result; + } + + private: + std::string m_key; + std::string m_value; + }; + + class ParseResult + { + public: + + ParseResult( + const std::shared_ptr< + std::unordered_map> + >, + std::vector, + bool allow_unrecognised, + int&, char**&); + + size_t + count(const std::string& o) const + { + auto iter = m_options->find(o); + if (iter == m_options->end()) + { + return 0; + } + + auto riter = m_results.find(iter->second); + + return riter->second.count(); + } + + const OptionValue& + operator[](const std::string& option) const + { + auto iter = m_options->find(option); + + if (iter == m_options->end()) + { + throw option_not_present_exception(option); + } + + auto riter = m_results.find(iter->second); + + return riter->second; + } + + const std::vector& + arguments() const + { + return m_sequential; + } + + private: + + void + parse(int& argc, char**& argv); + + void + add_to_option(const std::string& option, const std::string& arg); + + bool + consume_positional(std::string a); + + void + parse_option + ( + std::shared_ptr value, + const std::string& name, + const std::string& arg = "" + ); + + void + parse_default(std::shared_ptr details); + + void + checked_parse_arg + ( + int argc, + char* argv[], + int& current, + std::shared_ptr value, + const std::string& name + ); + + const std::shared_ptr< + std::unordered_map> + > m_options; + std::vector m_positional; + std::vector::iterator m_next_positional; + std::unordered_set m_positional_set; + std::unordered_map, OptionValue> m_results; + + bool m_allow_unrecognised; + + std::vector m_sequential; + }; + + class Options + { + typedef std::unordered_map> + OptionMap; + public: + + Options(std::string program, std::string help_string = "") + : m_program(std::move(program)) + , m_help_string(toLocalString(std::move(help_string))) + , m_custom_help("[OPTION...]") + , m_positional_help("positional parameters") + , m_show_positional(false) + , m_allow_unrecognised(false) + , m_options(std::make_shared()) + , m_next_positional(m_positional.end()) + { + } + + Options& + positional_help(std::string help_text) + { + m_positional_help = std::move(help_text); + return *this; + } + + Options& + custom_help(std::string help_text) + { + m_custom_help = std::move(help_text); + return *this; + } + + Options& + show_positional_help() + { + m_show_positional = true; + return *this; + } + + Options& + allow_unrecognised_options() + { + m_allow_unrecognised = true; + return *this; + } + + ParseResult + parse(int& argc, char**& argv); + + OptionAdder + add_options(std::string group = ""); + + void + add_option + ( + const std::string& group, + const std::string& s, + const std::string& l, + std::string desc, + std::shared_ptr value, + std::string arg_help + ); + + //parse positional arguments into the given option + void + parse_positional(std::string option); + + void + parse_positional(std::vector options); + + void + parse_positional(std::initializer_list options); + + template + void + parse_positional(Iterator begin, Iterator end) { + parse_positional(std::vector{begin, end}); + } + + std::string + help(const std::vector& groups = {}) const; + + const std::vector + groups() const; + + const HelpGroupDetails& + group_help(const std::string& group) const; + + private: + + void + add_one_option + ( + const std::string& option, + std::shared_ptr details + ); + + String + help_one_group(const std::string& group) const; + + void + generate_group_help + ( + String& result, + const std::vector& groups + ) const; + + void + generate_all_groups_help(String& result) const; + + std::string m_program; + String m_help_string; + std::string m_custom_help; + std::string m_positional_help; + bool m_show_positional; + bool m_allow_unrecognised; + + std::shared_ptr m_options; + std::vector m_positional; + std::vector::iterator m_next_positional; + std::unordered_set m_positional_set; + + //mapping from groups to help options + std::map m_help; + }; + + class OptionAdder + { + public: + + OptionAdder(Options& options, std::string group) + : m_options(options), m_group(std::move(group)) + { + } + + OptionAdder& + operator() + ( + const std::string& opts, + const std::string& desc, + std::shared_ptr value + = ::cxxopts::value(), + std::string arg_help = "" + ); + + private: + Options& m_options; + std::string m_group; + }; + + namespace + { + constexpr int OPTION_LONGEST = 30; + constexpr int OPTION_DESC_GAP = 2; + + std::basic_regex option_matcher + ("--([[:alnum:]][-_[:alnum:]]+)(=(.*))?|-([[:alnum:]]+)"); + + std::basic_regex option_specifier + ("(([[:alnum:]]),)?[ ]*([[:alnum:]][-_[:alnum:]]*)?"); + + String + format_option + ( + const HelpOptionDetails& o + ) + { + auto& s = o.s; + auto& l = o.l; + + String result = " "; + + if (s.size() > 0) + { + result += "-" + toLocalString(s) + ","; + } + else + { + result += " "; + } + + if (l.size() > 0) + { + result += " --" + toLocalString(l); + } + + auto arg = o.arg_help.size() > 0 ? toLocalString(o.arg_help) : "arg"; + + if (!o.is_boolean) + { + if (o.has_implicit) + { + result += " [=" + arg + "(=" + toLocalString(o.implicit_value) + ")]"; + } + else + { + result += " " + arg; + } + } + + return result; + } + + String + format_description + ( + const HelpOptionDetails& o, + size_t start, + size_t width + ) + { + auto desc = o.desc; + + if (o.has_default && (!o.is_boolean || o.default_value != "false")) + { + desc += toLocalString(" (default: " + o.default_value + ")"); + } + + String result; + + auto current = std::begin(desc); + auto startLine = current; + auto lastSpace = current; + + auto size = size_t{}; + + while (current != std::end(desc)) + { + if (*current == ' ') + { + lastSpace = current; + } + + if (*current == '\n') + { + startLine = current + 1; + lastSpace = startLine; + } + else if (size > width) + { + if (lastSpace == startLine) + { + stringAppend(result, startLine, current + 1); + stringAppend(result, "\n"); + stringAppend(result, start, ' '); + startLine = current + 1; + lastSpace = startLine; + } + else + { + stringAppend(result, startLine, lastSpace); + stringAppend(result, "\n"); + stringAppend(result, start, ' '); + startLine = lastSpace + 1; + } + size = 0; + } + else + { + ++size; + } + + ++current; + } + + //append whatever is left + stringAppend(result, startLine, current); + + return result; + } + } + +inline +ParseResult::ParseResult +( + const std::shared_ptr< + std::unordered_map> + > options, + std::vector positional, + bool allow_unrecognised, + int& argc, char**& argv +) +: m_options(options) +, m_positional(std::move(positional)) +, m_next_positional(m_positional.begin()) +, m_allow_unrecognised(allow_unrecognised) +{ + parse(argc, argv); +} + +inline +OptionAdder +Options::add_options(std::string group) +{ + return OptionAdder(*this, std::move(group)); +} + +inline +OptionAdder& +OptionAdder::operator() +( + const std::string& opts, + const std::string& desc, + std::shared_ptr value, + std::string arg_help +) +{ + std::match_results result; + std::regex_match(opts.c_str(), result, option_specifier); + + if (result.empty()) + { + throw invalid_option_format_error(opts); + } + + const auto& short_match = result[2]; + const auto& long_match = result[3]; + + if (!short_match.length() && !long_match.length()) + { + throw invalid_option_format_error(opts); + } else if (long_match.length() == 1 && short_match.length()) + { + throw invalid_option_format_error(opts); + } + + auto option_names = [] + ( + const std::sub_match& short_, + const std::sub_match& long_ + ) + { + if (long_.length() == 1) + { + return std::make_tuple(long_.str(), short_.str()); + } + else + { + return std::make_tuple(short_.str(), long_.str()); + } + }(short_match, long_match); + + m_options.add_option + ( + m_group, + std::get<0>(option_names), + std::get<1>(option_names), + desc, + value, + std::move(arg_help) + ); + + return *this; +} + +inline +void +ParseResult::parse_default(std::shared_ptr details) +{ + m_results[details].parse_default(details); +} + +inline +void +ParseResult::parse_option +( + std::shared_ptr value, + const std::string& /*name*/, + const std::string& arg +) +{ + auto& result = m_results[value]; + result.parse(value, arg); + + m_sequential.emplace_back(value->long_name(), arg); +} + +inline +void +ParseResult::checked_parse_arg +( + int argc, + char* argv[], + int& current, + std::shared_ptr value, + const std::string& name +) +{ + if (current + 1 >= argc) + { + if (value->value().has_implicit()) + { + parse_option(value, name, value->value().get_implicit_value()); + } + else + { + throw missing_argument_exception(name); + } + } + else + { + if (value->value().has_implicit()) + { + parse_option(value, name, value->value().get_implicit_value()); + } + else + { + parse_option(value, name, argv[current + 1]); + ++current; + } + } +} + +inline +void +ParseResult::add_to_option(const std::string& option, const std::string& arg) +{ + auto iter = m_options->find(option); + + if (iter == m_options->end()) + { + throw option_not_exists_exception(option); + } + + parse_option(iter->second, option, arg); +} + +inline +bool +ParseResult::consume_positional(std::string a) +{ + while (m_next_positional != m_positional.end()) + { + auto iter = m_options->find(*m_next_positional); + if (iter != m_options->end()) + { + auto& result = m_results[iter->second]; + if (!iter->second->value().is_container()) + { + if (result.count() == 0) + { + add_to_option(*m_next_positional, a); + ++m_next_positional; + return true; + } + else + { + ++m_next_positional; + continue; + } + } + else + { + add_to_option(*m_next_positional, a); + return true; + } + } + else + { + throw option_not_exists_exception(*m_next_positional); + } + } + + return false; +} + +inline +void +Options::parse_positional(std::string option) +{ + parse_positional(std::vector{std::move(option)}); +} + +inline +void +Options::parse_positional(std::vector options) +{ + m_positional = std::move(options); + m_next_positional = m_positional.begin(); + + m_positional_set.insert(m_positional.begin(), m_positional.end()); +} + +inline +void +Options::parse_positional(std::initializer_list options) +{ + parse_positional(std::vector(std::move(options))); +} + +inline +ParseResult +Options::parse(int& argc, char**& argv) +{ + ParseResult result(m_options, m_positional, m_allow_unrecognised, argc, argv); + return result; +} + +inline +void +ParseResult::parse(int& argc, char**& argv) +{ + int current = 1; + + int nextKeep = 1; + + bool consume_remaining = false; + + while (current != argc) + { + if (strcmp(argv[current], "--") == 0) + { + consume_remaining = true; + ++current; + break; + } + + std::match_results result; + std::regex_match(argv[current], result, option_matcher); + + if (result.empty()) + { + //not a flag + + // but if it starts with a `-`, then it's an error + if (argv[current][0] == '-' && argv[current][1] != '\0') { + if (!m_allow_unrecognised) { + throw option_syntax_exception(argv[current]); + } + } + + //if true is returned here then it was consumed, otherwise it is + //ignored + if (consume_positional(argv[current])) + { + } + else + { + argv[nextKeep] = argv[current]; + ++nextKeep; + } + //if we return from here then it was parsed successfully, so continue + } + else + { + //short or long option? + if (result[4].length() != 0) + { + const std::string& s = result[4]; + + for (std::size_t i = 0; i != s.size(); ++i) + { + std::string name(1, s[i]); + auto iter = m_options->find(name); + + if (iter == m_options->end()) + { + if (m_allow_unrecognised) + { + continue; + } + else + { + //error + throw option_not_exists_exception(name); + } + } + + auto value = iter->second; + + if (i + 1 == s.size()) + { + //it must be the last argument + checked_parse_arg(argc, argv, current, value, name); + } + else if (value->value().has_implicit()) + { + parse_option(value, name, value->value().get_implicit_value()); + } + else + { + //error + throw option_requires_argument_exception(name); + } + } + } + else if (result[1].length() != 0) + { + const std::string& name = result[1]; + + auto iter = m_options->find(name); + + if (iter == m_options->end()) + { + if (m_allow_unrecognised) + { + // keep unrecognised options in argument list, skip to next argument + argv[nextKeep] = argv[current]; + ++nextKeep; + ++current; + continue; + } + else + { + //error + throw option_not_exists_exception(name); + } + } + + auto opt = iter->second; + + //equals provided for long option? + if (result[2].length() != 0) + { + //parse the option given + + parse_option(opt, name, result[3]); + } + else + { + //parse the next argument + checked_parse_arg(argc, argv, current, opt, name); + } + } + + } + + ++current; + } + + for (auto& opt : *m_options) + { + auto& detail = opt.second; + auto& value = detail->value(); + + auto& store = m_results[detail]; + + if(!store.count() && value.has_default()){ + parse_default(detail); + } + } + + if (consume_remaining) + { + while (current < argc) + { + if (!consume_positional(argv[current])) { + break; + } + ++current; + } + + //adjust argv for any that couldn't be swallowed + while (current != argc) { + argv[nextKeep] = argv[current]; + ++nextKeep; + ++current; + } + } + + argc = nextKeep; + +} + +inline +void +Options::add_option +( + const std::string& group, + const std::string& s, + const std::string& l, + std::string desc, + std::shared_ptr value, + std::string arg_help +) +{ + auto stringDesc = toLocalString(std::move(desc)); + auto option = std::make_shared(s, l, stringDesc, value); + + if (s.size() > 0) + { + add_one_option(s, option); + } + + if (l.size() > 0) + { + add_one_option(l, option); + } + + //add the help details + auto& options = m_help[group]; + + options.options.emplace_back(HelpOptionDetails{s, l, stringDesc, + value->has_default(), value->get_default_value(), + value->has_implicit(), value->get_implicit_value(), + std::move(arg_help), + value->is_container(), + value->is_boolean()}); +} + +inline +void +Options::add_one_option +( + const std::string& option, + std::shared_ptr details +) +{ + auto in = m_options->emplace(option, details); + + if (!in.second) + { + throw option_exists_error(option); + } +} + +inline +String +Options::help_one_group(const std::string& g) const +{ + typedef std::vector> OptionHelp; + + auto group = m_help.find(g); + if (group == m_help.end()) + { + return ""; + } + + OptionHelp format; + + size_t longest = 0; + + String result; + + if (!g.empty()) + { + result += toLocalString(" " + g + " options:\n"); + } + + for (const auto& o : group->second.options) + { + if (m_positional_set.find(o.l) != m_positional_set.end() && + !m_show_positional) + { + continue; + } + + auto s = format_option(o); + longest = (std::max)(longest, stringLength(s)); + format.push_back(std::make_pair(s, String())); + } + + longest = (std::min)(longest, static_cast(OPTION_LONGEST)); + + //widest allowed description + auto allowed = size_t{76} - longest - OPTION_DESC_GAP; + + auto fiter = format.begin(); + for (const auto& o : group->second.options) + { + if (m_positional_set.find(o.l) != m_positional_set.end() && + !m_show_positional) + { + continue; + } + + auto d = format_description(o, longest + OPTION_DESC_GAP, allowed); + + result += fiter->first; + if (stringLength(fiter->first) > longest) + { + result += '\n'; + result += toLocalString(std::string(longest + OPTION_DESC_GAP, ' ')); + } + else + { + result += toLocalString(std::string(longest + OPTION_DESC_GAP - + stringLength(fiter->first), + ' ')); + } + result += d; + result += '\n'; + + ++fiter; + } + + return result; +} + +inline +void +Options::generate_group_help +( + String& result, + const std::vector& print_groups +) const +{ + for (size_t i = 0; i != print_groups.size(); ++i) + { + const String& group_help_text = help_one_group(print_groups[i]); + if (empty(group_help_text)) + { + continue; + } + result += group_help_text; + if (i < print_groups.size() - 1) + { + result += '\n'; + } + } +} + +inline +void +Options::generate_all_groups_help(String& result) const +{ + std::vector all_groups; + all_groups.reserve(m_help.size()); + + for (auto& group : m_help) + { + all_groups.push_back(group.first); + } + + generate_group_help(result, all_groups); +} + +inline +std::string +Options::help(const std::vector& help_groups) const +{ + String result = m_help_string + "\nUsage:\n " + + toLocalString(m_program) + " " + toLocalString(m_custom_help); + + if (m_positional.size() > 0 && m_positional_help.size() > 0) { + result += " " + toLocalString(m_positional_help); + } + + result += "\n\n"; + + if (help_groups.size() == 0) + { + generate_all_groups_help(result); + } + else + { + generate_group_help(result, help_groups); + } + + return toUTF8String(result); +} + +inline +const std::vector +Options::groups() const +{ + std::vector g; + + std::transform( + m_help.begin(), + m_help.end(), + std::back_inserter(g), + [] (const std::map::value_type& pair) + { + return pair.first; + } + ); + + return g; +} + +inline +const HelpGroupDetails& +Options::group_help(const std::string& group) const +{ + return m_help.at(group); +} + +} + +#endif //CXXOPTS_HPP_INCLUDED \ No newline at end of file diff --git a/tfidf/loadMemes.cpp b/tfidf/loadMemes.cpp new file mode 100644 index 0000000..8445046 --- /dev/null +++ b/tfidf/loadMemes.cpp @@ -0,0 +1,26 @@ +#include +#include +#include +#include + +#include "types.hpp" + +using namespace std; + +MemesList loadMemes(string memesPath) { + MemesList list; + + ifstream file(memesPath); + string line; + + regex pattern("MOTIF (.+?)_"); + smatch matches; + + while (getline(file, line)) { + if (regex_search(line, matches, pattern)) { + list.push_back(matches[1]); + } + } + + return list; +} \ No newline at end of file diff --git a/tfidf/loadScans.cpp b/tfidf/loadScans.cpp new file mode 100644 index 0000000..9079492 --- /dev/null +++ b/tfidf/loadScans.cpp @@ -0,0 +1,113 @@ +#include +#include +#include +#include +#include +#include + +#include "types.hpp" +#include "scans.hpp" +#include "meme.hpp" +#include "memeSample.hpp" +#include "utils.hpp" + +using namespace std; +void loadScan(Scans& scans, string& scanPath, SamplesBC& samplesToBC, string& bc, + bool isMulticlass, bool readRankAsScore) { + ifstream file(scanPath); + + auto memes = &scans.getMemes(); + auto samplesLabel = &scans.getSamplesLabel(); + auto sequencesCount = &scans.getSequencesCount(); + + regex filePattern("^.+/(.+?)_peptides_vs_(.+?)_motifs_"); + regex motifPattern("^MOTIF (.+?)_"); + regex hitsPattern("^([^ ]+) (.+)$"); + + smatch matches; + regex_search(scanPath, matches, filePattern); + if (bc.compare(matches[2]) != 0) { + return; // scan is not part of biological condition + } + + string other = "other"; + string sample = matches[1]; + auto isSampleOfBC = bc.compare(samplesToBC[sample]) == 0; + + if (isMulticlass) { + samplesLabel->insert(pair(sample, samplesToBC[sample])); + } else if (isSampleOfBC) { + samplesLabel->insert(pair(sample, bc)); + } else { + samplesLabel->insert(pair(matches[1], other)); + } + + string line; + Meme* meme; + MemeSample* memeSample; + while (getline(file, line)) { + if (regex_search(line, matches, motifPattern)) { + string motif = matches[1]; + auto memeIter = memes->find(motif); + if (memeIter == memes->end()) { + meme = new Meme(); + memes->insert(pair(motif, meme)); + } else { + meme = memeIter->second; + } + + getline(file, line); // hits + regex_search(line, matches, hitsPattern); + int hits = stoi(matches[2]); + memeSample = new MemeSample(hits); + // cout << "motif " << motif << ", sample hits: " << sample << " = " << hits << endl; + if (readRankAsScore) { + getline(file, line); // shuffles + getline(file, line); // rank + regex_search(line, matches, hitsPattern); + auto rank = stod(matches[2]); + memeSample->setScore(rank); + } + meme->addSample(sample, memeSample); + } + else if (regex_search(line, matches, hitsPattern)) // sequences + { + string sequence = matches[1]; + int hits = stoi(matches[2]); + + memeSample->addSequence(sequence, hits); + + auto sequencesCountIter = sequencesCount->find(sequence); + if (sequencesCountIter == sequencesCount->end()) { + sequencesCount->insert(pair(sequence, 1)); + } else { + sequencesCountIter->second += 1; + } + + if (isSampleOfBC) { + scans.getBCSequences().insert(sequence); + } + } + } +} + +Scans loadScans(string scanPath, string samples2bcPath, string& bc, + bool isMultiClass, bool readRankAsScore) { + Scans scans; + auto samplesToBC = loadSamplesToBC(samples2bcPath); + + struct dirent *entry = nullptr; + DIR *dp = nullptr; + dp = opendir(scanPath.c_str()); + if (dp != nullptr) { + while ((entry = readdir(dp))) { + string entryName(entry->d_name); + string entryPath = scanPath + entryName; + // cout << entryPath << endl; + loadScan(scans, entryPath, samplesToBC, bc, isMultiClass, readRankAsScore); + } + } + closedir(dp); + + return scans; +} diff --git a/tfidf/meme.hpp b/tfidf/meme.hpp new file mode 100644 index 0000000..181c664 --- /dev/null +++ b/tfidf/meme.hpp @@ -0,0 +1,18 @@ +#pragma once +#include "types.hpp" + +class Meme { +public: + Meme() { + } + + void addSample(string& sample, MemeSample* memeSample) { + this->_samples[sample] = memeSample; + } + + MemeSampleMap& getSamples() { + return this->_samples; + } +private: + MemeSampleMap _samples; +}; diff --git a/tfidf/memeSample.hpp b/tfidf/memeSample.hpp new file mode 100644 index 0000000..ff13dab --- /dev/null +++ b/tfidf/memeSample.hpp @@ -0,0 +1,43 @@ +#pragma once +#include "types.hpp" + +class MemeSample { +public: + MemeSample(int hitscount) : + _hitsCount(hitscount), + _score(0), + _maxSequenceCount(0) { + } + + void addSequence(string& sequence, int count) { + this->_sequences[sequence] = count; + if (count > this->_maxSequenceCount) { + this->_maxSequenceCount = count; + } + } + + int getHitsCount() { + return this->_hitsCount; + } + + SequencesCount& getSequences() { + return this->_sequences; + } + + int getMaxSequenceCount() { + return this->_maxSequenceCount; + } + + double getScore() { + return this->_score; + } + + void setScore(double score) { + this->_score = score; + } +private: + int _hitsCount; + SequencesCount _sequences; + int _maxSequenceCount; + double _score; +}; diff --git a/tfidf/scans.hpp b/tfidf/scans.hpp new file mode 100644 index 0000000..4200d66 --- /dev/null +++ b/tfidf/scans.hpp @@ -0,0 +1,29 @@ +#pragma once +#include "types.hpp" + +class Scans { +public: + Scans() { + } + + MemesMap& getMemes() { + return this->_memes; + } + + SamplesLabel& getSamplesLabel() { + return this->_samplesLabel; + } + + SequencesCount& getSequencesCount() { + return this->_sequencesMemeCount; + } + + SequencesSet& getBCSequences() { + return this->_bcSequences; + } +private: + MemesMap _memes; + SamplesLabel _samplesLabel; + SequencesCount _sequencesMemeCount; + SequencesSet _bcSequences; +}; diff --git a/tfidf/tfidf.cpp b/tfidf/tfidf.cpp new file mode 100644 index 0000000..72f7ead --- /dev/null +++ b/tfidf/tfidf.cpp @@ -0,0 +1,88 @@ +#include +#include +#include +#include +#include +#include + +#include "cxxopts.hpp" +#include "types.hpp" +#include "memeSample.hpp" +#include "meme.hpp" +#include "scans.hpp" +#include "utils.hpp" + +MemesList loadMemes(string memesPath); +Scans loadScans(string scanPath, string samples2bcPath, string& bc, bool isMultiClass, bool readRankAsScore); +void calculateScores(Scans& scans, TFMethod eMethod, float augmentFactor, bool isUseAllSequences); +void writeResults(MemesList& memes, Scans& scans, string& outputPath, string& bc); + +int main(int argc, char *argv[]) +{ + cxxopts::Options options("td-idf", "Calculate td-idf based on hits"); + options.add_options() + ("memes", "Ordered memes file", cxxopts::value()) + ("bc", "Biological condition", cxxopts::value()) + ("sam2bc", "Samples to biological condition mapping", cxxopts::value()) + ("scan", "Path to hits results directory", cxxopts::value()) + ("output", "Path to results (CSVs) directory", cxxopts::value()) + ("done", "Path to done file", cxxopts::value()) + ("rank", "Read and use rank in scan instead of calculating TF-IDF", cxxopts::value()->default_value("false")) + ("method", "TF method (boolean, terms, log, augmented), default is boolean", cxxopts::value()->default_value("boolean")) + ("factor", "Augment TF method factor (0-1), default is 0.5", cxxopts::value()->default_value("0.5")) + ("multiclass", "If labeling is multi-class or bc/other, default is false", cxxopts::value()->default_value("false")) + ("allseqs", "If use all sequences to score or just BC sequences, default is false", cxxopts::value()->default_value("false")) + ("v,verbose", "Verbose output", cxxopts::value()->default_value("false")); + + auto result = options.parse(argc, argv); + + auto memesPath = result["memes"].as(); + auto bc = result["bc"].as(); + auto samples2bcPath = result["sam2bc"].as(); + auto scanPath = result["scan"].as(); + auto outputPath = result["output"].as(); + auto donePath = result["done"].as(); + auto useRank = result["rank"].as(); + auto method = result["method"].as(); + bool isMultiClass = result["multiclass"].as(); + bool isUseAllSequences = result["allseqs"].as(); + float augmentFactor = result["factor"].as(); + + auto begin = chrono::steady_clock::now(); + + auto eMethod = parseMethod(method); + if (eMethod == TFMethod_NONE) { + cout << "Invalid TF method!" << endl; + return -1; + } + + if (scanPath[scanPath.size() - 1] != '/') { + scanPath += '/'; + } + + if (outputPath[outputPath.size() - 1] != '/') { + outputPath += '/'; + } + outputPath += bc + "/"; + mkdir(outputPath.c_str(), S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH); + + MemesList memes = loadMemes(memesPath); + Scans scans = loadScans(scanPath, samples2bcPath, bc, isMultiClass, useRank); + if (!useRank) { + calculateScores(scans, eMethod, augmentFactor, isUseAllSequences); + } + writeResults(memes, scans, outputPath, bc); + + cout << "total BC seq: " << scans.getBCSequences().size() << endl; + cout << "total seq: " << scans.getSequencesCount().size() << endl; + + auto end = chrono::steady_clock::now(); + + ofstream doneFile(donePath); + if (useRank) { + doneFile << "Merged using rank for " << bc << " biological condition" << endl; + } else { + doneFile << "Calculated TF-IDF for " << bc << " biological condition" << endl; + } + doneFile << "Completed in " << chrono::duration_cast(end - begin).count() << "[ms]" << endl; +} diff --git a/tfidf/types.hpp b/tfidf/types.hpp new file mode 100644 index 0000000..943153f --- /dev/null +++ b/tfidf/types.hpp @@ -0,0 +1,25 @@ +#pragma once +#include +#include +#include +#include +using namespace std; + +// types +enum TFMethod { + TFMethod_NONE, + TFMethod_BOOL, + TFMethod_TERMS, + TFMethod_LOG, + TFMethod_AUGMENT +}; + +class Meme; +class MemeSample; +typedef map SequencesCount; +typedef map SamplesLabel; +typedef map MemeSampleMap; +typedef map MemesMap; +typedef map SamplesBC; +typedef vector MemesList; +typedef set SequencesSet; diff --git a/tfidf/utils.cpp b/tfidf/utils.cpp new file mode 100644 index 0000000..75af0f6 --- /dev/null +++ b/tfidf/utils.cpp @@ -0,0 +1,50 @@ +#include "utils.hpp" +#include +#include +#include +#include + +using namespace std; +void tolower(string& str) { + for_each(str.begin(), str.end(), [](char & c){ + c = ::tolower(c); + }); +} + +TFMethod parseMethod(string method) { + tolower(method); + + if (method.compare("boolean") == 0) { + return TFMethod_BOOL; + } + + if (method.compare("terms") == 0) { + return TFMethod_TERMS; + } + + if (method.compare("log") == 0) { + return TFMethod_LOG; + } + + if (method.compare("augmented") == 0) { + return TFMethod_AUGMENT; + } + + return TFMethod_NONE; +} + +SamplesBC loadSamplesToBC(string samples2bcPath) { + SamplesBC samplesToBC; + ifstream file(samples2bcPath); + + string line; + smatch matches; + regex pattern("^([^#\t]+?)\\t(.+?)\\r?$"); + while (getline(file, line)) { + if (regex_search(line, matches, pattern)) { + // cout << matches[1] << ": " << matches[2] << endl; + samplesToBC[matches[1]] = matches[2]; + } + } + return samplesToBC; +} diff --git a/tfidf/utils.hpp b/tfidf/utils.hpp new file mode 100644 index 0000000..0b5b4ad --- /dev/null +++ b/tfidf/utils.hpp @@ -0,0 +1,8 @@ +#pragma once +#include +#include "types.hpp" + +using namespace std; +void tolower(string& str); +TFMethod parseMethod(string method); +SamplesBC loadSamplesToBC(string samples2bcPath); \ No newline at end of file diff --git a/tfidf/writeResults.cpp b/tfidf/writeResults.cpp new file mode 100644 index 0000000..f925b74 --- /dev/null +++ b/tfidf/writeResults.cpp @@ -0,0 +1,71 @@ +#include +#include +#include + +#include "types.hpp" +#include "scans.hpp" +#include "meme.hpp" +#include "memeSample.hpp" + +using namespace std; +void writeResults(MemesList& memes, Scans& scans, string& outputPath, string& bc) { + ofstream hitsFile(outputPath + bc + "_hits.csv"); + ofstream scoreFile(outputPath + bc + "_values.csv"); + auto header = "sample_name,label"; + hitsFile << header; + scoreFile << header; + + // Transpose for easier write + auto samplesCount = scans.getSamplesLabel().size(); + auto memesCount = scans.getMemes().size(); + auto hitsValues = new double[memesCount * samplesCount]; + auto scoreValues = new double[memesCount * samplesCount]; + + auto memesIter = memes.begin(); + auto memesEnd = memes.end(); + int memeIndex = 0; + while (memesIter != memesEnd) { + hitsFile << "," << *memesIter; + scoreFile << "," << *memesIter; + + auto meme = scans.getMemes().find(*memesIter); + + auto samplesIter = meme->second->getSamples().begin(); + auto samplesEnd = meme->second->getSamples().end(); + int sampleIndex = 0; + while (samplesIter != samplesEnd) { + auto hits = samplesIter->second->getHitsCount(); + auto score = samplesIter->second->getScore(); + hitsValues[sampleIndex * memesCount + memeIndex] = hits; + scoreValues[sampleIndex * memesCount + memeIndex] = score; + sampleIndex++; + samplesIter++; + } + memeIndex++; + memesIter++; + } + hitsFile << endl; + scoreFile << endl; + + auto samplesIter = scans.getSamplesLabel().begin(); + auto samplesEnd = scans.getSamplesLabel().end(); + int sampleIndex = 0; + while (samplesIter != samplesEnd) { + hitsFile << samplesIter->first << "," << samplesIter->second; + scoreFile << samplesIter->first << "," << samplesIter->second; + + auto memesIter = memes.begin(); + auto memesEnd = memes.end(); + int memeIndex = 0; + while (memesIter != memesEnd) { + hitsFile << "," << hitsValues[sampleIndex * memesCount + memeIndex]; + scoreFile << "," << scoreValues[sampleIndex * memesCount + memeIndex]; + memeIndex++; + memesIter++; + } + hitsFile << endl; + scoreFile << endl; + sampleIndex++; + samplesIter++; + } +} diff --git a/tools/change_meme_version.py b/tools/change_meme_version.py new file mode 100644 index 0000000..3ed6eaf --- /dev/null +++ b/tools/change_meme_version.py @@ -0,0 +1,102 @@ +import sys +import os + +from sklearn.decomposition import FactorAnalysis +if os.path.exists('/groups/pupko/orenavr2/'): + src_dir = '/groups/pupko/orenavr2/igomeProfilingPipeline/src' +elif os.path.exists('/Users/Oren/Dropbox/Projects/'): + src_dir = '/Users/Oren/Dropbox/Projects/gershoni/src' +else: + src_dir = '.' +sys.path.insert(0, src_dir) + +from auxiliaries.pipeline_auxiliaries import verify_file_is_not_empty + + +def write_to_file(meme_file_path, lines): + output_file = open(meme_file_path, 'w') + output_file.write(f'MEME version 4\n\n') + for line in lines: + output_file.write(line) + + +def split_to_files(meme_file_path, splitted_meme_dir, motifs_per_file): + with open(meme_file_path) as meme_f: + meta_info = '' + data = '' + motif_number = 0 + split_number = 0 + add_meta_info = True + for line in meme_f: + if add_meta_info: + if "MOTIF" not in line: + meta_info += line + continue + else: + add_meta_info = False + if line.startswith("MOTIF"): + if motif_number == motifs_per_file: + with open(f'{splitted_meme_dir}/{str(split_number).zfill(2)}.txt', 'w') as f: + f.write(meta_info + data) + data = '' + motif_number = 0 + split_number += 1 + motif_number += 1 + data += line + # don't forget last batch!! + with open(f'{splitted_meme_dir}/{str(split_number).zfill(2)}.txt', 'w') as f: + f.write(meta_info + data) + + +def get_num_lines_pssm(line): + return int(line.split()[5]) + + +def adjustment_meme_file(input_path_meme_file, output_path, motifs_per_file): + lines = [] + flag_background = False + meme_file = open(input_path_meme_file, 'r') + meme_file_lines = meme_file.readlines() + count_lines = 0 + for num_line,line in enumerate(meme_file_lines): + if num_line < count_lines and flag_background: + lines.append(line.strip()) + elif 'ALPHABET' in line: + lines.append(f'{line}\n') + elif 'Background letter frequencies' in line: + lines.append(f'Background letter frequencies\n') + flag_background = True + count_lines = num_line + 4 + elif 'position-specific probability matrix' in line: + flag_background = False + lines.append(f'\nMOTIF {line.split()[1]}\n') + line = meme_file_lines[num_line + 2 ] + num_lines_to_read = get_num_lines_pssm(line) + new_line = " ".join(line.split()[:8]) + lines.append(f'{new_line}\n') + pssm_lines = meme_file_lines[num_line + 3 : num_line + 3+ num_lines_to_read] + for line in pssm_lines: + lines.append(f'{line.lstrip()}') + lines.append('\n') + + meme_file_path = os.path.join(output_path, 'meme.txt') + write_to_file(meme_file_path, lines) + if meme_file_path: + verify_file_is_not_empty(meme_file_path) + splitted_meme_dir = os.path.join(os.path.split(meme_file_path)[0], 'memes') + os.makedirs(splitted_meme_dir, exist_ok=True) + split_to_files(meme_file_path, splitted_meme_dir, motifs_per_file) + + +if __name__ == '__main__': + print(f'Starting {sys.argv[0]}. Executed command is:\n{" ".join(sys.argv)}') + + import argparse + parser = argparse.ArgumentParser() + parser.add_argument('input_path_meme_file', type=str, help='A path to a meme file that need to change the version') + parser.add_argument('output_path', type=str, help='Path to put the output meme files.') + parser.add_argument('--motifs_per_file', type=int, default=5, help='How many pssm will be in one meme file') + args = parser.parse_args() + + adjustment_meme_file(args.input_path_meme_file, args.output_path, args.motifs_per_file) + \ No newline at end of file diff --git a/tools/cross_summary.py b/tools/cross_summary.py new file mode 100644 index 0000000..87ca769 --- /dev/null +++ b/tools/cross_summary.py @@ -0,0 +1,139 @@ +""" +Union distinctive and identified motifs to single table +""" +from os import path +import pandas as pd +import matplotlib.pyplot as plt +import numpy as np +import re + + +colors_map = { + 'perfect': 'green', + 'mixed': 'blue', + 'incorrect': 'red', + 'unexpected': 'orange', + 'artifact': 'red', + 'negative': 'orange', + 'irrelevant': 'grey' +} + + +cluster_pattern = re.compile('(.+?)_(.+)_clusterRank') + + +def load_union(path: str, base_name: str): + motifs = {} + with open(path, 'r') as f: + lines = f.readlines() + for line in lines: + clusters = line.split(',') + clustered = {} + for cluster in clusters: + cluster_match = cluster_pattern.match(cluster) + consensus = cluster_match.group(1) + biological_condition = cluster_match.group(2) + clustered[consensus] = 'base' if biological_condition == base_name else 'other' + for key in clustered: + union = { 'base': [], 'other': [] } + for other_key in clustered: + if key == other_key: continue + union[clustered[other_key]].append(other_key) + motifs[key] = union + return motifs + + +def render_mpl_table(data, col_width=3.0, row_height=0.625, font_size=14, + header_color='#40466e', row_colors=['#f1f1f2', 'w'], edge_color='w', + bbox=[0, 0, 1, 1], header_columns=0, color_from_index=2, + color_to_index = 2, output_path=None, ax=None, has_union_data=None, + **kwargs): + if ax is None: + size = (np.array(data.shape[::-1]) + np.array([0, 1])) * np.array([col_width, row_height]) + fig, ax = plt.subplots(figsize=size) + ax.axis('off') + cell_align = 'center' if has_union_data else 'left' + mpl_table = ax.table(cellText=data.values, bbox=bbox, colLabels=data.columns, cellLoc=cell_align, **kwargs) + if has_union_data: + mpl_table.auto_set_column_width(col=list(range(len(data.columns)))) + else: + mpl_table.auto_set_font_size(False) + mpl_table.set_fontsize(font_size) + + for k, cell in mpl_table._cells.items(): + cell.set_edgecolor(edge_color) + if k[0] == 0 or k[1] < header_columns: + cell.set_text_props(weight='bold', color='w') + cell.set_facecolor(header_color) + elif k[1] >= color_from_index and k[1] < color_to_index: + cell.set_facecolor(colors_map[data.iat[k[0] - 1, k[1]]]) + else: + cell.set_facecolor(row_colors[k[0]%len(row_colors) ]) + if output_path: + fig.savefig(output_path) + return ax.get_figure(), ax + +# TODO refactor +def process(results, top_motifs, union_data, output_base_path: str): + motifs = {} + # Index + for result in results: + source = result[0] + df = pd.read_csv(result[1]) + df = df[['motif', 'label']] + for _, row in df.iterrows(): + motif = row['motif'] + label = row['label'] + motif_data = None + try: + motif_data = motifs[motif] + except: + motif_data = { 'is_top': motif in top_motifs } + motifs[motif] = motif_data + motif_data[source] = label + # TODO extract details (mixes)? maybe with flag + # Flatten + motif_keys = list(motifs.keys()) + first_key = motif_keys[0] + columns = list(motifs[first_key].keys()) + data = [] + for motif in motif_keys: + motif_data = motifs[motif] + motif_flat_data = [motif] + for column in columns: motif_flat_data.append(motif_data[column]) + if union_data: + motif_flat_data.append(', '.join(union_data[motif]['base'])) + motif_flat_data.append(', '.join(union_data[motif]['other'])) + data.append(motif_flat_data) + # Save to csv + print('Saving results...') + output_path = f'{output_base_path}.csv' + cols = ['motif'] + columns + if union_data: cols += ['self_union', 'other_union'] + df = pd.DataFrame(data, columns=cols) + df.to_csv(output_path, index=False) + # Save table (svg) + output_path = f'{output_base_path}_all.svg' + has_union_data = union_data is not None + render_mpl_table(df, output_path=output_path, color_to_index=len(columns) + 1, has_union_data=has_union_data) + df_groupped = df.groupby('is_top') + for is_top, group in df_groupped: + name = 'top' if is_top else 'others' + output_path = f'{output_base_path}_{name}.svg' + group = group.drop('is_top', axis=1) + render_mpl_table(group, output_path=output_path, color_from_index=1, color_to_index=len(columns), has_union_data=has_union_data) + + +if __name__ == '__main__': + base_path = '/mnt/d/workspace/data/webiks/igome/results/exp4+9_all_half_gaps/model_fitting' + top_motifs = ['CSTTTTTRTC', 'CDTDGWIRMDPC', 'CPSHISLRNPC', 'CTRPTRPSLWMSPAC', 'CTNLCGTLAC', 'CCHHIFQRPPC', 'CPACSLFTC', 'CDAHSLFFPC', 'CGGTAGSFSRC', 'CLSSLFTRC'] + results = [ + ['base', path.join(base_path, 'ferret_texas/distinctive_motifs_all.csv')], + ['reduced', path.join(base_path,'ferret_texas/exp4_texas_motifs_on_exp8_reduced.csv')], + ['complete', path.join(base_path,'ferret_texas/exp4_texas_motifs_on_exp8_complete.csv')], + ] + base_name = 'ferret_texas' + union_data = load_union('/mnt/d/workspace/data/webiks/igome/results/exp4+9_all_half_gaps/model_inference/cross/texas_combined.csv', base_name) + output_base_path = path.join(base_path, 'ferret_texas/cross_summary') + process(results, top_motifs, None, output_base_path) + process(results, top_motifs, union_data, output_base_path + "_union") \ No newline at end of file diff --git a/tools/distinctive_motifs.py b/tools/distinctive_motifs.py new file mode 100644 index 0000000..9f1a122 --- /dev/null +++ b/tools/distinctive_motifs.py @@ -0,0 +1,189 @@ + +''' +Extract ranked distinctive motifs ignoring artifacts +''' +import pandas as pd +import matplotlib.pyplot as plt +import seaborn as sns +import sys +import logging +from matplotlib.patches import Patch +import os +if os.path.exists('/groups/pupko/orenavr2/'): + src_dir = '/groups/pupko/orenavr2/igomeProfilingPipeline/src' +elif os.path.exists('/Users/Oren/Dropbox/Projects/'): + src_dir = '/Users/Oren/Dropbox/Projects/gershoni/src' +else: + src_dir = '.' +sys.path.insert(0, src_dir) +from auxiliaries.pipeline_auxiliaries import log_scale + + +colors_map = { + 'perfect': 'green', + 'mixed': 'blue', + 'artifact': 'red', + 'negative': 'orange' +} + +def get_sorted_features(feature_importance_path: str): + with open(feature_importance_path, 'r') as f: + features = [line.strip().split('\t') for line in f.readlines()] + features = sorted(features, key=lambda x: float(x[1]), reverse=True) + return features + +def is_artifact(motif: str, df: pd.DataFrame, bio_cond: str, invalid_mix: str, score: float, order: int, logger: logging.Logger, txt_file_out: str): + data = df[['label', 'sample_name', motif]] + other_max = data.loc[data['label'] == 'other', motif].max() + bc_min = data.loc[data['label'] == bio_cond, motif].min() + artifact = bc_min < other_max + is_perfect = bc_min > other_max + is_valid_mix = True + mixed_samples = list(data.loc[(data['label'] == 'other') & (data[motif] >= bc_min), 'sample_name']) + if artifact: + message = f'Motif {motif} is artifact, other_max={other_max}, bc_min={bc_min}, importance score={score}, importance order={order}\n' + txt_file_out.write(message) if txt_file_out else logger.info(message) + elif is_perfect: + message = f'Motif {motif} is perfect, other_max={other_max}, bc_min={bc_min}, importance score={score}, importance order={order}\n' + txt_file_out.write(message) if txt_file_out else logger.info(message) + else: + if invalid_mix: + is_valid_mix = not any(invalid_mix in s for s in mixed_samples) + if is_valid_mix: + message = f'Motif {motif} is mixed, other_max={other_max}, bc_min={bc_min}, importance score={score}, importance order={order}\n' + txt_file_out.write(message) if txt_file_out else logger.info(message) + else: + message = f'Motif {motif} is invalid mix, other_max={other_max}, bc_min={bc_min}, importance score={score}, importance order={order}\n' + txt_file_out.write(message) if txt_file_out else logger.info(message) + # TODO convert mixed samples to mixed bc inc. count + return artifact, is_perfect, is_valid_mix, mixed_samples + + +def generate_heatmap(base_path: str, df: pd.DataFrame, colors, title: str , rank_method: str, logger: logging.Logger): + logger.info('Generating heatmap...') + df.set_index('sample_name', inplace=True) + df = log_scale(df, rank_method) + + map_path = f'{base_path}.svg' + number_of_samples = df.shape[0] + + map = sns.clustermap(df, cmap="Blues", col_cluster=False, yticklabels=True, col_colors=colors) + plt.setp(map.ax_heatmap.yaxis.get_majorticklabels(), fontsize=150 / number_of_samples) + handles = [Patch(facecolor=colors_map[name]) for name in colors_map] + plt.legend(handles, colors_map, title='Type', bbox_to_anchor=(1, 1), bbox_transform=plt.gcf().transFigure, loc='upper right') + map.ax_heatmap.set_title(title, pad=25, fontsize=14) + map.savefig(map_path, format='svg', bbox_inches="tight") + plt.close() + + +def save_output(base_path: str, data, logger: logging.Logger): + logger.info('Saving results...') + output_path = f'{base_path}.csv' + df = pd.DataFrame(data, columns=['motif', 'label', 'is_artifact', 'is_perfect', 'is_valid_mix', 'mixed_samples', 'importance', 'order']) + df.to_csv(output_path, index=False) + + +def extract_distinctive_motifs(data_path: str, count: int, feature_importance_path: str, biological_condition: str, output_base_path: str, title_heatmap: str, + invalid_mix: str, epsilon: float, min_importance_score: float, rank_method: str, logger: logging.Logger): + logger.info("Loading features...") + features = get_sorted_features(feature_importance_path) + data_df = pd.read_csv(data_path) + + total = 0 + last_score = 0 + last_order = 0 + distinctive_motifs = [] + i = 0 + perfect_count = 0 + artifact_count = 0 + invalid_mix_count = 0 + mixed_count = {} + colors = [] + output = [] + output_label = '' + txt_file_out = open(f'{output_base_path}.txt', 'w') if output_base_path else None + for feature in features: + i += 1 + motif = feature[0] + score = float(feature[1]) + if score < min_importance_score: + break + artifact, is_perfect, is_valid_mix, mixed_samples = is_artifact(motif, data_df, biological_condition, invalid_mix, score, i, logger, txt_file_out) + if total >= count and score + epsilon < last_score: + break + if artifact: + artifact_count += 1 + colors.append(colors_map['artifact']) + output_label = 'artifact' + output.append([motif, output_label, artifact, is_perfect, is_valid_mix, mixed_samples, score, i]) + continue + if not is_valid_mix: + invalid_mix_count += 1 + colors.append(colors_map['negative']) + output_label = 'negative' + output.append([motif, output_label, artifact, is_perfect, is_valid_mix, mixed_samples, score, i]) + continue + if is_perfect: + perfect_count += 1 + colors.append(colors_map['perfect']) + output_label = 'perfect' + else: + colors.append(colors_map['mixed']) + output_label = 'mixed' + for sample in mixed_samples: + sample_count = 0 + try: + sample_count = mixed_count[sample] + except: + pass + mixed_count[sample] = sample_count + 1 + distinctive_motifs.append(motif) + output.append([motif, output_label, artifact, is_perfect, is_valid_mix, mixed_samples, score, i]) + last_order = i + total += 1 + if total == count: + last_score = score + + summary = f'\nDistinctive motifs ({len(distinctive_motifs)}/{last_order} tested): {distinctive_motifs}\n' + \ + f'Perfects count: {perfect_count}\n' + \ + f'Mixed count: {mixed_count}\n' + \ + f'Filtered: Artifacts count: {artifact_count}, Invalid mixes count: {invalid_mix_count}\n' + + if output_base_path: + txt_file_out.write(summary) + txt_file_out.close() + + columns = ['sample_name'] + [x[0] for x in features[:count]] + generate_heatmap(output_base_path, data_df[columns], colors, title_heatmap, rank_method, logger) + save_output(output_base_path, output, logger) + else: + logger.info(summary) + + +if __name__ == '__main__': + print(f'Starting {sys.argv[0]}. Executed command is:\n{" ".join(sys.argv)}') + + import argparse + parser = argparse.ArgumentParser() + parser.add_argument('data_path', type=str, help='A path for motif ranking CSV') + parser.add_argument('count', type=int, help='Number of motifs to extract, e.g. 10 for top 10') + parser.add_argument('feature_importance_path', type=str, help='A path for the feature importance path for specific biological condition') + parser.add_argument('biological_condition', type=str, help='Biological condition name') + parser.add_argument('title_heatmap', type=str, help='A title for generated heatmap') + parser.add_argument('--output_base_path', type=str, help='An optional base path for generated files, extensions will be added to the base path') + parser.add_argument('--invalid_mix',type=str, default=None, help='A argument to know if there is compare to naive') + parser.add_argument('--epsilon', type=int, default=0, help='range of mistake') + parser.add_argument('--min_importance_score',type=int, default=0, help='The minimun score of motifs to take') + parser.add_argument('--rank_method', type=str, choices=['pval', 'tfidf', 'shuffles', 'hits'], default='hits', help='Motifs ranking method') + parser.add_argument('-v', '--verbose', action='store_true', help='Increase output verbosity') + args = parser.parse_args() + + if args.verbose: + logging.basicConfig(level=logging.DEBUG) + else: + logging.basicConfig(level=logging.INFO) + logger = logging.getLogger('main') + + extract_distinctive_motifs(args.data_path, args.count, args.feature_importance_path, args.biological_condition, args.output_base_path, args.title_heatmap, + args.invalid_mix, args.epsilon, args.min_importance_score, args.rank_method, logger) + \ No newline at end of file diff --git a/tools/extract_weblogos.py b/tools/extract_weblogos.py new file mode 100644 index 0000000..49f8ae5 --- /dev/null +++ b/tools/extract_weblogos.py @@ -0,0 +1,72 @@ +from os import path +import sys +import logging +import pandas as pd +if path.exists('/groups/pupko/orenavr2/'): + src_dir = '/groups/pupko/orenavr2/igomeProfilingPipeline/src' +elif path.exists('/Users/Oren/Dropbox/Projects/'): + src_dir = '/Users/Oren/Dropbox/Projects/gershoni/src' +else: + src_dir = '.' +sys.path.insert(0, src_dir) +from motif_inference.generate_weblogo import generate_weblogo as create_weblogo + + +def extract_mapping(file_path: str): + mapping = {} + with open(file_path, 'r') as f: + lines = f.readlines() + for line in lines: + if line.startswith('MOTIF '): + parts = line.split(' ')[1].split('_') + mapping[parts[0]] = '_'.join(parts[1:]).rstrip() + return mapping + + +def generate_weblogo(motif: str, mapping: dict, cleaned_path: str, output_path: str): + # Extract meme name + faa_file = mapping[motif] + meme_path = path.join(cleaned_path, faa_file) + weblogo_output_path = path.join(output_path, f'MOTIF_{motif}_{faa_file}') + logger.info(f'Generating weblogo for {motif}') + create_weblogo(meme_path, weblogo_output_path, motif) + + +def extract_weblogos(memes_path, cleaned_path, output_path, done_file_path, motifs_name_path, argv): + mapping = extract_mapping(memes_path) + motifs = [] + if motifs_name_path is None: + motifs = mapping.keys() + else: + df_motifs_names = pd.read_csv(motifs_name_path) + motifs = list(df_motifs_names.columns)[1:] # not take the sample name column. + + for motif in motifs: + generate_weblogo(motif, mapping, cleaned_path, output_path) + + with open(done_file_path, 'w') as f: + f.write(' '.join(argv) + '\n') + + +if __name__ == "__main__": + print(f'Starting {sys.argv[0]}. Executed command is:\n{" ".join(sys.argv)}') + + import argparse + + parser = argparse.ArgumentParser() + parser.add_argument('meme_file_path', type=str, help='A path to a meme file') + parser.add_argument('cleaned_path', type=str, help='A csv file with data matrix to model') + parser.add_argument('output_path', type=str, help='A csv file with data matrix to model') + parser.add_argument('done_file_path', type=str, help='A path to a file that signals that the module finished running successfully') + parser.add_argument('--motifs_name_path', type=str, default=None, help='Path for csv file that contain the names of motifs to generate weblogo') + parser.add_argument('-v', '--verbose', action='store_true', help='Increase output verbosity') + args = parser.parse_args() + + import logging + if args.verbose: + logging.basicConfig(level=logging.DEBUG) + else: + logging.basicConfig(level=logging.WARNING) + logger = logging.getLogger('main') + + extract_weblogos(args.meme_file_path, args.cleaned_path, args.output_path, args.done_file_path, args.motifs_name_path, sys.argv) diff --git a/tools/generate_heat_map.py b/tools/generate_heat_map.py new file mode 100644 index 0000000..cbfbc80 --- /dev/null +++ b/tools/generate_heat_map.py @@ -0,0 +1,42 @@ +''' +Extract ranked distinctive motifs ignoring artifacts +''' +from os import path +import pandas as pd +import matplotlib.pyplot as plt +import seaborn as sns + + +def generate_heatmap(base_path: str, df: pd.DataFrame, colors, title: str): + print('Generating heatmap...') + + df.set_index('sample_name', inplace=True) + map_path = f'{base_path}.svg' + number_of_samples = df.shape[0] + + map = sns.clustermap(df, cmap="Blues", col_cluster=False, yticklabels=True, col_colors=colors) + plt.setp(map.ax_heatmap.yaxis.get_majorticklabels(), fontsize=150 / number_of_samples) + map.ax_heatmap.set_title(title, pad=25, fontsize=14) + map.savefig(map_path, format='svg', bbox_inches="tight") + plt.close() + + +def process(values_path: str, motifs, colors, heatmap_title: str, output_base_path: str): + values = pd.read_csv(values_path) + columns = ['sample_name'] + motifs + generate_heatmap(output_base_path, values[columns], colors, heatmap_title) + + +if __name__ == '__main__': + base_path = '/mnt/d/workspace/data/webiks/igome/results/exp4+9_all_half_gaps/model_fitting' + values_path = path.join(base_path, 'Texas_2012/Texas_2012_values_exp4_complete.csv') + output_base_path = path.join(base_path, 'Texas_2012/cross_final_exp4_complete') + heatmap_title = 'Exp8 cross best motifs on Exp4' + # Green - top10 and perfect + # Blue - top10 and mixed, + # LightGreen - unexpected perfect + # Orange - unexpected and mixed + motifs = ['ADLAFRWGGM', 'SLFLHELTKSSG', 'LYFTNPPEPC', 'FQEWDMTRHS', 'WLADPSRVRGT', 'SRSAFLYPPP', 'CMNFNQNSSC', 'ARSPTNANIS', 'CLEWPSSIANC'] + colors = ['green', 'green', 'orange', 'orange', 'orange', 'orange', 'orange', 'orange', 'orange'] + process(values_path, motifs, colors, heatmap_title, output_base_path) + diff --git a/tools/identify.py b/tools/identify.py new file mode 100644 index 0000000..cd6d45a --- /dev/null +++ b/tools/identify.py @@ -0,0 +1,148 @@ +""" +Extract identified biological conditions per motif +""" +from os import path +import pandas as pd +import matplotlib.pyplot as plt +import seaborn as sns + + +colors_map = { + 'perfect': 'green', + 'mixed': 'blue', + 'incorrect': 'red', + 'unexpected': 'orange', + 'artifact': 'red', + 'negative': 'orange', + 'irrelevant': 'grey' +} + + +def load_samples_map(samples_to_bio_conds_path: str): + with open(samples_to_bio_conds_path, 'r') as f: + return dict([line.strip().split('\t') for line in f.readlines()]) + + +def save_output(base_path: str, data): + print('Saving results...') + output_path = f'{base_path}.csv' + df = pd.DataFrame(data, columns=['motif', 'label', 'is_perfect', 'is_positive', 'is_negative', 'identifies']) + df.to_csv(output_path, index=False) + + +def generate_heatmap(base_path: str, df: pd.DataFrame, colors, title: str): + print('Generating heatmap...') + + df.set_index('sample_name', inplace=True) + map_path = f'{base_path}.svg' + number_of_samples = df.shape[0] + + map = sns.clustermap(df, cmap="Blues", col_cluster=False, yticklabels=True, col_colors=colors) + plt.setp(map.ax_heatmap.yaxis.get_majorticklabels(), fontsize=150 / number_of_samples) + map.ax_heatmap.set_title(title, pad=25, fontsize=14) + map.savefig(map_path, format='svg', bbox_inches="tight") + plt.close() + + +def identify(values_path: str, samples_to_bio_conds_path: str, assumed_motifs, positive_bio_cond: str, negative_bio_cond: str, output_base_path: str, heatmap_title: str): + samples_to_bio_conds = load_samples_map(samples_to_bio_conds_path) + values = pd.read_csv(values_path) + values['label'] = [samples_to_bio_conds[sample] for sample in values['sample_name']] + motifs = list(values.columns)[2:] + + total_positive = 0 + total_negative = 0 + total_others = 0 + total_perfect = 0 + total_per_bio_cond = {} + assumption_in = [] + assumption_out = [] + assumption_others = [] + output = [] + colors = [] + motif_label = '' + for motif in motifs: + is_negative = False + is_positive = False + is_perfect = False + data = values[['label', motif]].groupby("label") + scored_data = 2 * data[motif].max() + (1 - data[motif].std()) # TODO add configurable weights + scored_data.sort_values(0, False, True) + identified_bio_conds = [] + best_value = None + for label, value in scored_data.iteritems(): + if not identified_bio_conds: + best_value = value + identified_bio_conds.append(label) + elif value == best_value: + identified_bio_conds.append(label) + else: + break + if positive_bio_cond: + is_positive = any(positive_bio_cond.lower() in s.lower() for s in identified_bio_conds) + if negative_bio_cond: + is_negative = any(negative_bio_cond.lower() in s.lower() for s in identified_bio_conds) + if is_positive: + total_positive += 1 + if len(identified_bio_conds) == 1: is_perfect = True + if is_negative: total_negative += 1 + if is_perfect: total_perfect += 1 + if not is_positive and not is_negative: total_others += 1 + for bio_cond in identified_bio_conds: + count = 0 + try: + count = total_per_bio_cond[bio_cond] + except: + pass + total_per_bio_cond[bio_cond] = count + 1 + if assumed_motifs: + is_assumed = motif in assumed_motifs + if is_assumed and is_positive and not is_negative: + assumption_in.append(motif) + motif_label = 'perfect' if is_perfect else 'mixed' + elif is_assumed: + assumption_out.append(motif) + motif_label = 'incorrect' + elif is_positive and not is_negative: + assumption_others.append(motif) + motif_label = 'unexpected' + else: + motif_label = 'irrelevant' + else: + if is_perfect: + motif_label = 'perfect' + elif is_positive and not is_negative: + motif_label = 'mixed' + elif is_negative and not is_positive: + motif_label = 'negative' + else: + motif_label = 'artifact' + output.append([motif, motif_label, is_perfect, is_positive, is_negative, identified_bio_conds]) + colors.append(colors_map[motif_label]) + print(f'{motif}: positive={is_positive}, negative={is_negative}, perfect={is_perfect}, identifies={identified_bio_conds}') + + print(f'\nTotal motifs={len(motifs)}, positives={total_positive}, negative={total_negative}, others={total_others}, perfects={total_perfect}') + print(f'Count per biological condition: {total_per_bio_cond}') + if assumed_motifs: + print(f'\nAssumed count motifs={len(assumed_motifs)}, in={len(assumption_in)}, out={len(assumption_out)}, not expected={len(assumption_others)}') + if assumption_in: print(f'Correctly assumed: {assumption_in}') + if assumption_out: print(f'Incorrectly assumed: {assumption_out}') + if assumption_others: print(f'Unexpected positives: {assumption_others}') + if output_base_path: + columns = ['sample_name'] + motifs + generate_heatmap(output_base_path, values[columns], colors, heatmap_title) + save_output(output_base_path, output) + + +if __name__ == '__main__': + base_path = '/mnt/d/workspace/data/webiks/igome/results/exp4+9_all_half_gaps/model_fitting' + assumed_motifs = ['CVGFVCTGPV', 'CNSNLSRSENL', 'FYSRTQYKFCEVGC', 'QTPYGRHPTL', 'YPKQDQPDIA', 'FFGALRP', 'CGSNYPRLSNI', 'CSGAAFEKYM', 'RDRPYMVDSPVN', 'YTRSSGMLIDC'] + values_path = path.join(base_path, 'Naive/Naive_values_exp4_complete.csv') + hits_path = path.join(base_path, 'Naive/Naive_hits_exp4_complete.csv') # TODO check hits "back-up" + samples_to_bio_conds_path = path.join(base_path, 'exp4+9_all_samples2bioconds.txt') + positive_bio_cond = 'naive' # 'texas' + negative_bio_cond = None # 'naive' + output_base_path = path.join(base_path, 'Naive/exp8_naive_motifs_on_exp4_complete') + heatmap_title = 'Exp8 Naive Motifs on Exp4 all samples' + + identify(values_path, samples_to_bio_conds_path, assumed_motifs, positive_bio_cond, negative_bio_cond, output_base_path, heatmap_title) \ No newline at end of file diff --git a/tools/intersect.py b/tools/intersect.py new file mode 100644 index 0000000..cc5e67a --- /dev/null +++ b/tools/intersect.py @@ -0,0 +1,8 @@ +source = ['CGSLKPLSC', 'GGSLRPGSS', 'CGGSLRPAC', 'GGSLRPTASS', 'CGSLKPTPSPC', 'SAGSLRPS', 'CGSLKGTAQAPC', 'CHSMADITKGGC', 'RLGGSLSST', 'CGSLRLSSLAF', 'PYRKNPVDGLTP', 'CGSLKGQFQSNC', 'CTDGMPSRLVDVC', 'CGSLKLTPSNSLY', 'CGRLDPFLNC', 'CGGPLRTLSC', 'CSLAGTLRVPC', 'CGSLRACT', 'LCGSLRVCP', 'CGGGSLRTGFNC', 'SLKPTIMTSP', 'CLQLCQVW'] +target = ['GGSLRPGSS', 'GGSLRPTASS', 'CGSLRAALLC', 'CGSLKGTAQAPC', 'CHSMADITKGGC', 'CGSLRLSSLAF', 'PYRKNPVDGLTP', 'CASLKPNPEC', 'CDGSTTPSSC', 'CGSLKLTPSNSLY', 'CGRLDPFLNC', 'CGSLKDTSYC', 'CGSLRACT', 'CADSTSPSAL', 'LSNIGPC', 'EPADSPSMLS', 'CYRGMAPQGC'] +raw_intersect = ['CGSLKPLSC', 'GGSLRPGSS', 'CGSLKPTPSPC', 'CGSLKGTAQAPC', 'CGSLKLTPSNSLY', 'CGRLDPFLNC', 'CGGPLRTLSC', 'CSLAGTLRVPC', 'LCGSLRVCP'] + +intersections = [x for x in target if x in source] +print('intersections', intersections) +best_intersections = [x for x in intersections if x in raw_intersect] +print('best', best_intersections) \ No newline at end of file diff --git a/tools/merge_meme.py b/tools/merge_meme.py new file mode 100644 index 0000000..3f29084 --- /dev/null +++ b/tools/merge_meme.py @@ -0,0 +1,59 @@ +''' +Merge meme files from different samples/biological conditions +''' +from os import path, listdir, popen + + +def is_valid_dir(dir, prefixes, ignore_suffixes): + is_valid_prefix = False + for prefix in prefixes: + if dir.startswith(prefix): + is_valid_prefix = True + break + if not is_valid_prefix: return False + + is_valid_suffix = True + for suffix in ignore_suffixes: + if dir.endswith(suffix): + is_valid_suffix = False + break + + return is_valid_suffix + + +def merge(base_path, output_path, prefix, ignore_suffix): + print('Merging...') + is_first = True + files = [path.join(base_path, dir, 'meme.txt') for dir in listdir(base_path) if \ + is_valid_dir(dir, prefix, ignore_suffix)] + with open(output_path, 'w') as w: + for file in files: + with open(file, 'r') as r: + if is_first: + data = r.readlines() # First time include headers + is_first = False + else: + data = r.readlines()[6:] # Ignore meme header + w.writelines(data) + + +def unite(meme_path, cluster_output_path): + print('Uniting...') + unite_pssm_script_path = './UnitePSSMs/UnitePSSMs' + aln_cutoff = 20 + pcc_cutoff = 0.6 + + cmd = f'{unite_pssm_script_path} -pssm {meme_path} -out {cluster_output_path} ' \ + f'-aln_cutoff {aln_cutoff} -pcc_cutoff {pcc_cutoff}' + stream = popen(cmd) + output = stream.read() + print(output) + +if __name__ == '__main__': + base_path = '/mnt/d/workspace/data/webiks/igome/results/hiv/exp_7_8_top2_mi' + prefix = ['Top_EC'] + ignore_suffix = ['_1', '_2', '_3'] + meme_output_path = path.join(base_path, 'merged_meme.txt') + cluster_output_path = path.join(base_path, 'merged_clusters.csv') + merge(base_path, meme_output_path, prefix, ignore_suffix) + unite(meme_output_path, cluster_output_path) diff --git a/tools/perfect.py b/tools/perfect.py new file mode 100644 index 0000000..048fe41 --- /dev/null +++ b/tools/perfect.py @@ -0,0 +1,76 @@ +import pandas as pd + +def simple_separation(file_path: str, filterIn: str = None, filterOut: str = None, bc_override: str = None): + data = pd.read_csv(file_path, engine='python') + if filterIn: + data = data[data.sample_name.str.contains(filterIn)] + if filterOut: + data = data[~data.sample_name.str.contains(filterOut)] + if bc_override: + data.loc[data.sample_name.str.contains(bc_override), 'label'] = 'bc' + data = data.transpose() + # print(data['sample_name'][0]) + j = 0 + labels = [] + bc_label = '' + perfect_motifs = [] + artifact_motifs = [] + mixed_motifs = [] + for tup in data.itertuples(): + j += 1 + if j == 1: # Samples + print('labels', tup[1:]) + continue + if j == 2: # groups + labels = list(tup[1:]) + groups = pd.unique(labels) + print('groups', groups) + bc_label = [group for group in groups if group != other_label][0] + continue + motif = tup[0] + values = { key: [] for key in groups } + for index, value in enumerate(tup[1:]): + values[labels[index]].append(value) + + bc_min = min(values[bc_label]) + bc_max = max(values[bc_label]) + other_min = min(values[other_label]) + other_max = max(values[other_label]) + + if bc_min > other_max: + perfect_motifs.append(motif) + elif bc_max < other_min: # should be <= ? + artifact_motifs.append(motif) + else: + mixed_motifs.append(motif) + + return { 'perfect': perfect_motifs, 'artifact': artifact_motifs, 'mixed': mixed_motifs } + + +def separation_containment(values, hits): + contained = { key: [] for key in values } + for key in values: + for motif in values[key]: + if motif in hits[key]: + contained[key].append(motif) + return contained + + +values_path = '/mnt/d/workspace/data/webiks/igome/results/exp4+9_all_half_gaps/model_fitting/ferret_texas/ferret_texas_values.csv' +hits_path = '/mnt/d/workspace/data/webiks/igome/results/exp4+9_all_half_gaps/model_fitting/ferret_texas/ferret_texas_hits.csv' +other_label = 'other' + +filterIn = 'Sanofi' # None, 'mAb', 'IgG' +filterOut = None # 'Sanofi' +bc_override = None # 'Texas_2012' # 'cov001', 'cov002' +values_separation = simple_separation(values_path, filterIn, filterOut, bc_override) +hits_separation = simple_separation(hits_path, filterIn, filterOut, bc_override) +contained = separation_containment(values_separation, hits_separation) + +print('perfects', values_separation['perfect']) +print('artifacts', values_separation['artifact']) +if len(values_separation['perfect']) == len(contained['perfect']): + print('All perfects are contained in hits') +else: + print('Contained in hits (perfect)', contained['perfect']) + print('Not contained in hits (perfect)', [x for x in values_separation['perfect'] if x not in contained['perfect']]) diff --git a/tools/predict.py b/tools/predict.py new file mode 100644 index 0000000..211bd3e --- /dev/null +++ b/tools/predict.py @@ -0,0 +1,88 @@ +import sys +import os +import argparse +import pandas as pd +import joblib +import logging +logging.basicConfig(level=logging.INFO) +logger = logging.getLogger('main') + + +def predict(data_path, model_paths, output_path): + data = pd.read_csv(data_path) + os.makedirs(output_path, exist_ok=True) + model_with_perfect_classification = [] + # e.g., ..../model_fitting/17b/17b_significant_pvalues_model/best_model/Top_1_features_model.pkl + for model_path in sorted(model_paths, key=lambda x: -int(x.split('/')[-1].split('_')[1])): + number_of_features = int(os.path.split(model_path)[-1].split('_')[1]) + + feature_names = pd.read_csv(model_path.replace('_model.pkl', '.csv')).drop(['sample_name'], axis=1).columns + assert len(feature_names) == number_of_features, f'{len(feature_names)} != {number_of_features}' + sample_name = data['sample_name'] + X = data.drop(['label', 'sample_name'], axis=1).reindex(feature_names, axis=1).values + assert X.shape[0] > 1, 'No samples to predict...' + y = data['label'] + model = joblib.load(model_path) + model_score = model.score(X, y) + error_rate = 1-model_score + if error_rate > 0: + logger.debug(f'{model_path} has error rate - {error_rate}') + predictions = model.predict(X) + errors = predictions != y + results = pd.DataFrame({'sample names': sample_name, + 'predictions': predictions, + 'true labels': y, + 'error': errors}) + logger.info(f'Model with *{number_of_features}* features has {sum(errors)} prediction errors') + logger.info('\n' + results.reindex(['sample names', 'predictions', 'true labels', 'error'], axis=1).to_string() + '\n\n') + output = f'{output_path}/Top_{number_of_features}_model_predictions.txt' + with open(output, 'w') as f: + f.write(f'Error rate: {error_rate}\n') + f.write(f'sample name\ttrue label\tprediction\tprediction score\n') + for i in range(len(predictions)): + f.write(f'{sample_name[i]}\t{y[i]}\t{predictions[i]}\t{int(errors[i])}\n') + else: + model_with_perfect_classification.append(str(number_of_features)) + + if error_rate: + new_file = False + if not os.path.exists(f'{output_path}/error_rates.txt'): + new_file = True + with open(f'{output_path}/error_rates.txt', 'a') as f: + if new_file: + f.write(f'number_of_features\terror_rate\tnum_of_errors\n') + f.write(f'{number_of_features}\t\t\t{error_rate}\t\t{errors.sum()}\n') + + logger.debug(f'Prediction error rate is {error_rate} (for debugging)') + + with open(f'{output_path}/perfect_classifiers.txt', 'a') as f: + f.write('\n'.join(model_with_perfect_classification[::-1])) + + +if __name__ == '__main__': + + logger.info(f'Starting {sys.argv[0]}. Executed command is:\n{" ".join(sys.argv)}') + + parser = argparse.ArgumentParser() + parser.add_argument('model_path', type=lambda x: x.rstrip('/'), help='A path to a pickel file with a ML model') + parser.add_argument('data_path', type=str, help='A path to a file with samples to predict') + parser.add_argument('output_path', type=str, help='A path to dir to print all results') + parser.add_argument('-v', '--verbose', action='store_true', help='Increase output verbosity') + args = parser.parse_args() + + if args.verbose: + logging.basicConfig(level=logging.DEBUG) + else: + logging.basicConfig(level=logging.INFO) + logger = logging.getLogger('main') + + if os.path.isdir(args.model_path): + # in case its a folder with several models + model_paths = [f'{args.model_path}/{file_name}' for file_name in os.listdir(args.model_path) if + file_name.startswith('Top') and file_name.endswith('pkl')] + else: + model_paths = [args.model_path] + + logger.info('\n' + '#' * 70 + '\nMAKE SURE TO PREDICT WITH THE SAME FEATURES THE MODEL USES!\n' + '#' * 70) + + predict(args.data_path, model_paths, args.output_path) diff --git a/tools/sequencesToCsv.py b/tools/sequencesToCsv.py new file mode 100644 index 0000000..09bdf44 --- /dev/null +++ b/tools/sequencesToCsv.py @@ -0,0 +1,39 @@ +import argparse + + +def sequences_to_csv(input_path, output_path): + with open(input_path, 'r') as f: + lines = f.readlines() + + gaps = [] + count = 0 + + with open(output_path, 'w') as f: + for i in range(1, len(lines), 2): + count += 1 + chars = list(lines[i].rstrip()) + if len(gaps) == 0: + gaps = [0 for c in chars] + for j in range(len(chars)): + if chars[j] == '-': + gaps[j] += 1 + f.write(','.join(chars)) + f.write('\n') + + for i in range(len(gaps)): + gaps[i] /= count + + f.write('\n') + f.write(','.join([str(gap) for gap in gaps])) + f.write('\n') + + print(gaps) + + +if __name__ == "__main__": + parser = argparse.ArgumentParser() + parser.add_argument('faa', help='A path to faa file with sequence') + parser.add_argument('csv', help='Output path of sequences separated by letters') + args = parser.parse_args() + + sequences_to_csv(args.faa, args.csv) diff --git a/tools/tool_united_heatmap.py b/tools/tool_united_heatmap.py new file mode 100644 index 0000000..4c03e30 --- /dev/null +++ b/tools/tool_united_heatmap.py @@ -0,0 +1,123 @@ +import sys +import pandas as pd +import os +import logging +import seaborn as sns +import matplotlib.pyplot as plt +from matplotlib.patches import Patch + +logging.basicConfig(level=logging.INFO) +logger = logging.getLogger('main') + +if os.path.exists('/groups/pupko/orenavr2/'): + src_dir = '/groups/pupko/orenavr2/igomeProfilingPipeline/src' +elif os.path.exists('/Users/Oren/Dropbox/Projects/'): + src_dir = '/Users/Oren/Dropbox/Projects/gershoni/src' +else: + src_dir = '.' +sys.path.insert(0, src_dir) + +from auxiliaries.pipeline_auxiliaries import load_table_to_dict, log_scale + + +def get_motifs_importance(biological_condition, bc_dir_path, rank_type, min_num_motifs, max_num_motifs, max_difference_from_last_motif, max_difference_from_fitst_motif): + important_motifs = [] + motif_importance_path = os.path.join(bc_dir_path, f'{biological_condition}_{rank_type}_model', 'best_model', 'feature_importance.txt') + dict_importance = load_table_to_dict(motif_importance_path, 'Motif {} is not unique!!') + first_val = None + last_val = 0 + for key, value in dict_importance.items(): + if first_val is None: + first_val = float(value) + last_val = first_val + important_motifs.append(key) + continue + current_val = float(value) + if len(important_motifs) >= max_num_motifs or first_val - current_val >= max_difference_from_fitst_motif or last_val - current_val >= max_difference_from_last_motif: + break + important_motifs.append(key) + last_val = current_val + if len(important_motifs) <= min_num_motifs and len(dict_importance.keys())>= min_num_motifs: + important_motifs = list(dict_importance.keys())[:min_num_motifs] + return important_motifs + +def united_csv(input_path, samplename2biologicalcondition_path, min_num_motifs, max_num_motifs, max_difference_from_last_motif, max_difference_from_fitst_motif): + samplename2biologicalcondition = load_table_to_dict(samplename2biologicalcondition_path, 'Barcode {} belongs to more than one sample_name!!') + biological_conditions = sorted(set(samplename2biologicalcondition.values())) + color_name = sns.color_palette(None, len(biological_conditions)) + dict_color = dict(zip(biological_conditions, color_name)) + list_hits = [] + list_values = [] + color_motifs_hits = [] + color_motifs_values = [] + for bc, color in zip(biological_conditions, color_name): + bc_dir_path = os.path.join(input_path, bc) + list_motifs_hits = get_motifs_importance(bc, bc_dir_path, 'hits', min_num_motifs, max_num_motifs, max_difference_from_last_motif, max_difference_from_fitst_motif) + list_motifs_values = get_motifs_importance(bc, bc_dir_path, 'values', min_num_motifs, max_num_motifs, max_difference_from_last_motif, max_difference_from_fitst_motif) + bc_hits = os.path.join(bc_dir_path, f'{bc}_hits.csv') + bc_values = os.path.join(bc_dir_path, f'{bc}_values.csv') + df_hits = pd.read_csv(bc_hits, index_col=0) + df_value = pd.read_csv(bc_values, index_col=0) + df_hits = df_hits[list_motifs_hits] + df_value = df_value[list_motifs_values] + + number_of_motifs_hits = len(list_motifs_hits) + number_of_motifs_values = len(list_motifs_values) + color_motifs_hits.append([color] * number_of_motifs_hits) + color_motifs_values.append([color] * number_of_motifs_values) + list_hits.append(df_hits) + list_values.append(df_value) + #merge all the df to one df. + hits_all=pd.concat(list_hits, axis=1) + values_all = pd.concat(list_values, axis=1) + hits_all = hits_all.reset_index() + values_all = values_all.reset_index() + + color_motifs_hits = sum(color_motifs_hits, []) + color_motifs_values = sum(color_motifs_values, []) + return hits_all, values_all, color_motifs_hits, color_motifs_values, dict_color + +def generate_heat_map(df, rank_method, number_of_samples, output_path, color_list, dict_color): + df = df.set_index(df.columns[0]) + train_data = log_scale(df, rank_method) + cm = sns.clustermap(train_data, cmap="Blues", col_cluster=False, yticklabels=True, col_colors=color_list) + plt.setp(cm.ax_heatmap.yaxis.get_majorticklabels(), fontsize=150/number_of_samples) + handles = [Patch(facecolor=dict_color[bc]) for bc in dict_color] + plt.legend(handles, dict_color, title='BC', + bbox_to_anchor=(1, 1), bbox_transform=plt.gcf().transFigure, loc='upper right') + cm.ax_heatmap.set_title(f"A heat-map of the all biological condition discriminatory motifs", pad=25.0) + cm.savefig(f"{output_path}.svg", format='svg', bbox_inches="tight") + plt.close() + +def united_heatmap(data_path, output_path, samplename2biologicalcondition_path, min_num_motifs, max_num_motifs, max_difference_from_last_motif, max_difference_from_fitst_motif, rank_method): + + hits, values, color_motifs_hits, color_motifs_values, dict_color = united_csv(data_path, samplename2biologicalcondition_path, min_num_motifs, max_num_motifs, + max_difference_from_last_motif, max_difference_from_fitst_motif) + output_path_hits = os.path.join(output_path,'hits_all_bc') + output_path_values = os.path.join(output_path,'values_all_bc') + generate_heat_map(hits, 'hits', len(hits), output_path_hits, color_motifs_hits, dict_color) + generate_heat_map(values, rank_method, len(values), output_path_values, color_motifs_values, dict_color) + +if __name__ == '__main__': + print(f'Starting {sys.argv[0]}. Executed command is:\n{" ".join(sys.argv)}') + import argparse + parser = argparse.ArgumentParser() + parser.add_argument('data_path', type=str, help='A path for all the csv files with data matrix to model ') + parser.add_argument('output_path', type=str, help='A path for folder to put the output') + parser.add_argument('samplename2biologicalcondition_path', type=str, help='Path for a file samplename2biologicalcondition') + parser.add_argument('--min_num_motifs', type=int, default=1, help='Minimum number of motifs to united from every BC') + parser.add_argument('--max_num_motifs', type=int, default=10, help='Maximum number of motifs to united from every BC') + parser.add_argument('--max_difference_from_last_motif', type=float, default=0.01, help='Take the motif if the difference of his importent values is less than the last motif') + parser.add_argument('--max_difference_from_fitst_motif', type=float, default=0.05, help='Take the motif if the difference of his importent values is less than the first motif') + parser.add_argument('--rank_method', choices=['pval', 'tfidf', 'shuffles'], default='shuffles', help='Rank method') + parser.add_argument('-v', '--verbose', action='store_true', help='Increase output verbosity') + args = parser.parse_args() + + if args.verbose: + logging.basicConfig(level=logging.DEBUG) + else: + logging.basicConfig(level=logging.INFO) + logger = logging.getLogger('main') + + united_heatmap(args.data_path, args.output_path, args.samplename2biologicalcondition_path, args.min_num_motifs, args.max_num_motifs, + args.max_difference_from_last_motif, args.max_difference_from_fitst_motif, args.rank_method) diff --git a/tools/unite_checker.py b/tools/unite_checker.py new file mode 100644 index 0000000..4cabe5b --- /dev/null +++ b/tools/unite_checker.py @@ -0,0 +1,27 @@ +import re + +cluster_pattern = re.compile('(.+?)_(.+)_clusterRank') + +def test(path: str): + with open(path, 'r') as f: + lines = f.readlines() + i = 0 + for line in lines: + i += 1 + clusters = line.split(',') + biological_condition_groupping = {} + for cluster in clusters: + cluster_match = cluster_pattern.match(cluster) + consensus = cluster_match.group(1) + biological_condition = cluster_match.group(2) + try: + biological_condition_groupping[biological_condition].append(consensus) + except: + biological_condition_groupping[biological_condition] = [consensus] + if len(biological_condition_groupping.keys()) > 1: + print(f'Cluster {i}, {biological_condition_groupping}') + + +if __name__ == '__main__': + file_path = '/mnt/d/workspace/data/webiks/igome/results/exp4+9_all_half_gaps/model_inference/cross/texas_combined.csv' + test(file_path) diff --git a/worker.py b/worker.py new file mode 100644 index 0000000..c1ae89a --- /dev/null +++ b/worker.py @@ -0,0 +1,15 @@ +# export CELERY_BROKER_URL="pyamqp://guest@localhost//" +# celery -A worker worker -P eventlet --prefetch-multiplier=1 --loglevel=info # -c 1000 -n worker1@%h +from celery import Celery +from time import sleep +from eventlet.green.subprocess import Popen + +app = Celery('tasks') + +@app.task(acks_late=True) +def submit(args, shell = False): + print(f'got task: args={args}, shell={shell}') + proc = Popen(args, shell=shell) + result = proc.wait() + print(f'end task: args={args}, shell={shell}, result=${result}') + return result diff --git a/worker_entrypoint.sh b/worker_entrypoint.sh new file mode 100644 index 0000000..411cc35 --- /dev/null +++ b/worker_entrypoint.sh @@ -0,0 +1,4 @@ +#!/bin/bash +source .venv/bin/activate +celery -A worker worker -P eventlet --prefetch-multiplier=1 --loglevel=info +