<SYSTEM_TASK:>
Solve the following problem using Python, implementing the functions described below, one line at a time
<END_TASK>
<USER_TASK:>
Description:
 def align_dna(job, fastqs, sample_type, univ_options, bwa_options):
 """ A wrapper for the entire dna alignment subgraph. :param list fastqs: The input fastqs for alignment :param str sample_type: Description of the sample to inject into the filename :param dict univ_options: Dict of universal options used by almost all tools :param dict bwa_options: Options specific to bwa :return: Dict containing output bam and bai output_files: |- '<sample_type>_fix_pg_sorted.bam': fsID +- '<sample_type>_fix_pg_sorted.bam.bai': fsID :rtype: dict """

    # The mkdup and regroup steps use picard that allots heap space using the Xmx key in the
    # univ_options dictionary. This should reflect in the job allotment. Since We want all these
    # jobs to occur on the same node, we ened to give them all the same memory requirements.

    bwa = job.wrapJobFn(run_bwa, fastqs, sample_type, univ_options, bwa_options,
                        disk=PromisedRequirement(bwa_disk, fastqs, bwa_options['index']),
                        memory=univ_options['java_Xmx'],
                        cores=bwa_options['n'])
    sam2bam = job.wrapJobFn(bam_conversion, bwa.rv(), sample_type, univ_options,
                            bwa_options['samtools'],
                            disk=PromisedRequirement(sam2bam_disk, bwa.rv()),
                            memory=univ_options['java_Xmx'])
    # reheader takes the same disk as sam2bam so we can serialize this on the same worker.
    reheader = job.wrapJobFn(fix_bam_header, sam2bam.rv(), sample_type, univ_options,
                             bwa_options['samtools'],
                             disk=PromisedRequirement(sam2bam_disk, bwa.rv()),
                             memory=univ_options['java_Xmx'])
    regroup = job.wrapJobFn(add_readgroups, reheader.rv(), sample_type, univ_options,
                            bwa_options['picard'],
                            disk=PromisedRequirement(regroup_disk, reheader.rv()),
                            memory=univ_options['java_Xmx'])
    mkdup = job.wrapJobFn(mark_duplicates, regroup.rv(), sample_type, univ_options,
                          bwa_options['picard'],
                          disk=PromisedRequirement(mkdup_disk, regroup.rv()),
                          memory=univ_options['java_Xmx'])
    index = job.wrapJobFn(index_bamfile, mkdup.rv(), sample_type, univ_options,
                          bwa_options['samtools'], sample_info='fix_pg_sorted',
                          disk=PromisedRequirement(index_disk, mkdup.rv()),
                          memory=univ_options['java_Xmx'])
    job.addChild(bwa)
    bwa.addChild(sam2bam)
    sam2bam.addChild(reheader)
    reheader.addChild(regroup)
    regroup.addChild(mkdup)
    mkdup.addChild(index)
    return index.rv()