pax_global_header00006660000000000000000000000064140045512760014516gustar00rootroot0000000000000052 comment=6f200c7ee78fb8553157819abcdf5caf22b59b3a malt-0.5.2/000077500000000000000000000000001400455127600124575ustar00rootroot00000000000000malt-0.5.2/.gitignore000066400000000000000000000006011400455127600144440ustar00rootroot00000000000000# Class files class/ *.class # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml hs_err_pid* # intellij *.iml .idea #eclipse .settings .classpath .project # MacOS .DS_Store # LaTeX auxiliary files *.aux *.blg *.idx *.ilg *.ind *.log *.out *.toc # antbuild: antbuild/*.jar antbuild/*rename* antbuild/*shrink* antbuild/src/ antbuild/classes/ malt-0.5.2/LICENSE000066400000000000000000000770471400455127600135030ustar00rootroot00000000000000GNU GENERAL PUBLIC LICENSE Version 3, 29 June 2007 Copyright © 2007 Free Software Foundation, Inc. Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. Preamble The GNU General Public License is a free, copyleft license for software and other kinds of works. The licenses for most software and other practical works are designed to take away your freedom to share and change the works. By contrast, the GNU General Public License is intended to guarantee your freedom to share and change all versions of a program--to make sure it remains free software for all its users. We, the Free Software Foundation, use the GNU General Public License for most of our software; it applies also to any other work released this way by its authors. You can apply it to your programs, too. When we speak of free software, we are referring to freedom, not price. Our General Public Licenses are designed to make sure that you have the freedom to distribute copies of free software (and charge for them if you wish), that you receive source code or can get it if you want it, that you can change the software or use pieces of it in new free programs, and that you know you can do these things. To protect your rights, we need to prevent others from denying you these rights or asking you to surrender the rights. Therefore, you have certain responsibilities if you distribute copies of the software, or if you modify it: responsibilities to respect the freedom of others. For example, if you distribute copies of such a program, whether gratis or for a fee, you must pass on to the recipients the same freedoms that you received. You must make sure that they, too, receive or can get the source code. And you must show them these terms so they know their rights. Developers that use the GNU GPL protect your rights with two steps: (1) assert copyright on the software, and (2) offer you this License giving you legal permission to copy, distribute and/or modify it. For the developers' and authors' protection, the GPL clearly explains that there is no warranty for this free software. For both users' and authors' sake, the GPL requires that modified versions be marked as changed, so that their problems will not be attributed erroneously to authors of previous versions. Some devices are designed to deny users access to install or run modified versions of the software inside them, although the manufacturer can do so. This is fundamentally incompatible with the aim of protecting users' freedom to change the software. The systematic pattern of such abuse occurs in the area of products for individuals to use, which is precisely where it is most unacceptable. Therefore, we have designed this version of the GPL to prohibit the practice for those products. If such problems arise substantially in other domains, we stand ready to extend this provision to those domains in future versions of the GPL, as needed to protect the freedom of users. Finally, every program is threatened constantly by software patents. States should not allow patents to restrict development and use of software on general-purpose computers, but in those that do, we wish to avoid the special danger that patents applied to a free program could make it effectively proprietary. To prevent this, the GPL assures that patents cannot be used to render the program non-free. The precise terms and conditions for copying, distribution and modification follow. TERMS AND CONDITIONS 0. Definitions. “This License” refers to version 3 of the GNU General Public License. “Copyright” also means copyright-like laws that apply to other kinds of works, such as semiconductor masks. “The Program” refers to any copyrightable work licensed under this License. Each licensee is addressed as “you”. “Licensees” and “recipients” may be individuals or organizations. To “modify” a work means to copy from or adapt all or part of the work in a fashion requiring copyright permission, other than the making of an exact copy. The resulting work is called a “modified version” of the earlier work or a work “based on” the earlier work. A “covered work” means either the unmodified Program or a work based on the Program. To “propagate” a work means to do anything with it that, without permission, would make you directly or secondarily liable for infringement under applicable copyright law, except executing it on a computer or modifying a private copy. Propagation includes copying, distribution (with or without modification), making available to the public, and in some countries other activities as well. To “convey” a work means any kind of propagation that enables other parties to make or receive copies. Mere interaction with a user through a computer network, with no transfer of a copy, is not conveying. An interactive user interface displays “Appropriate Legal Notices” to the extent that it includes a convenient and prominently visible feature that (1) displays an appropriate copyright notice, and (2) tells the user that there is no warranty for the work (except to the extent that warranties are provided), that licensees may convey the work under this License, and how to view a copy of this License. If the interface presents a list of user commands or options, such as a menu, a prominent item in the list meets this criterion. 1. Source Code. The “source code” for a work means the preferred form of the work for making modifications to it. “Object code” means any non-source form of a work. A “Standard Interface” means an interface that either is an official standard defined by a recognized standards body, or, in the case of interfaces specified for a particular programming language, one that is widely used among developers working in that language. The “System Libraries” of an executable work include anything, other than the work as a whole, that (a) is included in the normal form of packaging a Major Component, but which is not part of that Major Component, and (b) serves only to enable use of the work with that Major Component, or to implement a Standard Interface for which an implementation is available to the public in source code form. A “Major Component”, in this context, means a major essential component (kernel, window system, and so on) of the specific operating system (if any) on which the executable work runs, or a compiler used to produce the work, or an object code interpreter used to run it. The “Corresponding Source” for a work in object code form means all the source code needed to generate, install, and (for an executable work) run the object code and to modify the work, including scripts to control those activities. However, it does not include the work's System Libraries, or general-purpose tools or generally available free programs which are used unmodified in performing those activities but which are not part of the work. For example, Corresponding Source includes interface definition files associated with source files for the work, and the source code for shared libraries and dynamically linked subprograms that the work is specifically designed to require, such as by intimate data communication or control flow between those subprograms and other parts of the work. The Corresponding Source need not include anything that users can regenerate automatically from other parts of the Corresponding Source. The Corresponding Source for a work in source code form is that same work. 2. Basic Permissions. All rights granted under this License are granted for the term of copyright on the Program, and are irrevocable provided the stated conditions are met. This License explicitly affirms your unlimited permission to run the unmodified Program. The output from running a covered work is covered by this License only if the output, given its content, constitutes a covered work. This License acknowledges your rights of fair use or other equivalent, as provided by copyright law. You may make, run and propagate covered works that you do not convey, without conditions so long as your license otherwise remains in force. You may convey covered works to others for the sole purpose of having them make modifications exclusively for you, or provide you with facilities for running those works, provided that you comply with the terms of this License in conveying all material for which you do not control copyright. Those thus making or running the covered works for you must do so exclusively on your behalf, under your direction and control, on terms that prohibit them from making any copies of your copyrighted material outside their relationship with you. Conveying under any other circumstances is permitted solely under the conditions stated below. Sublicensing is not allowed; section 10 makes it unnecessary. 3. Protecting Users' Legal Rights From Anti-Circumvention Law. No covered work shall be deemed part of an effective technological measure under any applicable law fulfilling obligations under article 11 of the WIPO copyright treaty adopted on 20 December 1996, or similar laws prohibiting or restricting circumvention of such measures. When you convey a covered work, you waive any legal power to forbid circumvention of technological measures to the extent such circumvention is effected by exercising rights under this License with respect to the covered work, and you disclaim any intention to limit operation or modification of the work as a means of enforcing, against the work's users, your or third parties' legal rights to forbid circumvention of technological measures. 4. Conveying Verbatim Copies. You may convey verbatim copies of the Program's source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice; keep intact all notices stating that this License and any non-permissive terms added in accord with section 7 apply to the code; keep intact all notices of the absence of any warranty; and give all recipients a copy of this License along with the Program. You may charge any price or no price for each copy that you convey, and you may offer support or warranty protection for a fee. 5. Conveying Modified Source Versions. You may convey a work based on the Program, or the modifications to produce it from the Program, in the form of source code under the terms of section 4, provided that you also meet all of these conditions: a) The work must carry prominent notices stating that you modified it, and giving a relevant date. b) The work must carry prominent notices stating that it is released under this License and any conditions added under section 7. This requirement modifies the requirement in section 4 to “keep intact all notices”. c) You must license the entire work, as a whole, under this License to anyone who comes into possession of a copy. This License will therefore apply, along with any applicable section 7 additional terms, to the whole of the work, and all its parts, regardless of how they are packaged. This License gives no permission to license the work in any other way, but it does not invalidate such permission if you have separately received it. d) If the work has interactive user interfaces, each must display Appropriate Legal Notices; however, if the Program has interactive interfaces that do not display Appropriate Legal Notices, your work need not make them do so. A compilation of a covered work with other separate and independent works, which are not by their nature extensions of the covered work, and which are not combined with it such as to form a larger program, in or on a volume of a storage or distribution medium, is called an “aggregate” if the compilation and its resulting copyright are not used to limit the access or legal rights of the compilation's users beyond what the individual works permit. Inclusion of a covered work in an aggregate does not cause this License to apply to the other parts of the aggregate. 6. Conveying Non-Source Forms. You may convey a covered work in object code form under the terms of sections 4 and 5, provided that you also convey the machine-readable Corresponding Source under the terms of this License, in one of these ways: a) Convey the object code in, or embodied in, a physical product (including a physical distribution medium), accompanied by the Corresponding Source fixed on a durable physical medium customarily used for software interchange. b) Convey the object code in, or embodied in, a physical product (including a physical distribution medium), accompanied by a written offer, valid for at least three years and valid for as long as you offer spare parts or customer support for that product model, to give anyone who possesses the object code either (1) a copy of the Corresponding Source for all the software in the product that is covered by this License, on a durable physical medium customarily used for software interchange, for a price no more than your reasonable cost of physically performing this conveying of source, or (2) access to copy the Corresponding Source from a network server at no charge. c) Convey individual copies of the object code with a copy of the written offer to provide the Corresponding Source. This alternative is allowed only occasionally and noncommercially, and only if you received the object code with such an offer, in accord with subsection 6b. d) Convey the object code by offering access from a designated place (gratis or for a charge), and offer equivalent access to the Corresponding Source in the same way through the same place at no further charge. You need not require recipients to copy the Corresponding Source along with the object code. If the place to copy the object code is a network server, the Corresponding Source may be on a different server (operated by you or a third party) that supports equivalent copying facilities, provided you maintain clear directions next to the object code saying where to find the Corresponding Source. Regardless of what server hosts the Corresponding Source, you remain obligated to ensure that it is available for as long as needed to satisfy these requirements. e) Convey the object code using peer-to-peer transmission, provided you inform other peers where the object code and Corresponding Source of the work are being offered to the general public at no charge under subsection 6d. A separable portion of the object code, whose source code is excluded from the Corresponding Source as a System Library, need not be included in conveying the object code work. A “User Product” is either (1) a “consumer product”, which means any tangible personal property which is normally used for personal, family, or household purposes, or (2) anything designed or sold for incorporation into a dwelling. In determining whether a product is a consumer product, doubtful cases shall be resolved in favor of coverage. For a particular product received by a particular user, “normally used” refers to a typical or common use of that class of product, regardless of the status of the particular user or of the way in which the particular user actually uses, or expects or is expected to use, the product. A product is a consumer product regardless of whether the product has substantial commercial, industrial or non-consumer uses, unless such uses represent the only significant mode of use of the product. “Installation Information” for a User Product means any methods, procedures, authorization keys, or other information required to install and execute modified versions of a covered work in that User Product from a modified version of its Corresponding Source. The information must suffice to ensure that the continued functioning of the modified object code is in no case prevented or interfered with solely because modification has been made. If you convey an object code work under this section in, or with, or specifically for use in, a User Product, and the conveying occurs as part of a transaction in which the right of possession and use of the User Product is transferred to the recipient in perpetuity or for a fixed term (regardless of how the transaction is characterized), the Corresponding Source conveyed under this section must be accompanied by the Installation Information. But this requirement does not apply if neither you nor any third party retains the ability to install modified object code on the User Product (for example, the work has been installed in ROM). The requirement to provide Installation Information does not include a requirement to continue to provide support service, warranty, or updates for a work that has been modified or installed by the recipient, or for the User Product in which it has been modified or installed. Access to a network may be denied when the modification itself materially and adversely affects the operation of the network or violates the rules and protocols for communication across the network. Corresponding Source conveyed, and Installation Information provided, in accord with this section must be in a format that is publicly documented (and with an implementation available to the public in source code form), and must require no special password or key for unpacking, reading or copying. 7. Additional Terms. “Additional permissions” are terms that supplement the terms of this License by making exceptions from one or more of its conditions. Additional permissions that are applicable to the entire Program shall be treated as though they were included in this License, to the extent that they are valid under applicable law. If additional permissions apply only to part of the Program, that part may be used separately under those permissions, but the entire Program remains governed by this License without regard to the additional permissions. When you convey a copy of a covered work, you may at your option remove any additional permissions from that copy, or from any part of it. (Additional permissions may be written to require their own removal in certain cases when you modify the work.) You may place additional permissions on material, added by you to a covered work, for which you have or can give appropriate copyright permission. Notwithstanding any other provision of this License, for material you add to a covered work, you may (if authorized by the copyright holders of that material) supplement the terms of this License with terms: a) Disclaiming warranty or limiting liability differently from the terms of sections 15 and 16 of this License; or b) Requiring preservation of specified reasonable legal notices or author attributions in that material or in the Appropriate Legal Notices displayed by works containing it; or c) Prohibiting misrepresentation of the origin of that material, or requiring that modified versions of such material be marked in reasonable ways as different from the original version; or d) Limiting the use for publicity purposes of names of licensors or authors of the material; or e) Declining to grant rights under trademark law for use of some trade names, trademarks, or service marks; or f) Requiring indemnification of licensors and authors of that material by anyone who conveys the material (or modified versions of it) with contractual assumptions of liability to the recipient, for any liability that these contractual assumptions directly impose on those licensors and authors. All other non-permissive additional terms are considered “further restrictions” within the meaning of section 10. If the Program as you received it, or any part of it, contains a notice stating that it is governed by this License along with a term that is a further restriction, you may remove that term. If a license document contains a further restriction but permits relicensing or conveying under this License, you may add to a covered work material governed by the terms of that license document, provided that the further restriction does not survive such relicensing or conveying. If you add terms to a covered work in accord with this section, you must place, in the relevant source files, a statement of the additional terms that apply to those files, or a notice indicating where to find the applicable terms. Additional terms, permissive or non-permissive, may be stated in the form of a separately written license, or stated as exceptions; the above requirements apply either way. 8. Termination. You may not propagate or modify a covered work except as expressly provided under this License. Any attempt otherwise to propagate or modify it is void, and will automatically terminate your rights under this License (including any patent licenses granted under the third paragraph of section 11). However, if you cease all violation of this License, then your license from a particular copyright holder is reinstated (a) provisionally, unless and until the copyright holder explicitly and finally terminates your license, and (b) permanently, if the copyright holder fails to notify you of the violation by some reasonable means prior to 60 days after the cessation. Moreover, your license from a particular copyright holder is reinstated permanently if the copyright holder notifies you of the violation by some reasonable means, this is the first time you have received notice of violation of this License (for any work) from that copyright holder, and you cure the violation prior to 30 days after your receipt of the notice. Termination of your rights under this section does not terminate the licenses of parties who have received copies or rights from you under this License. If your rights have been terminated and not permanently reinstated, you do not qualify to receive new licenses for the same material under section 10. 9. Acceptance Not Required for Having Copies. You are not required to accept this License in order to receive or run a copy of the Program. Ancillary propagation of a covered work occurring solely as a consequence of using peer-to-peer transmission to receive a copy likewise does not require acceptance. However, nothing other than this License grants you permission to propagate or modify any covered work. These actions infringe copyright if you do not accept this License. Therefore, by modifying or propagating a covered work, you indicate your acceptance of this License to do so. 10. Automatic Licensing of Downstream Recipients. Each time you convey a covered work, the recipient automatically receives a license from the original licensors, to run, modify and propagate that work, subject to this License. You are not responsible for enforcing compliance by third parties with this License. An “entity transaction” is a transaction transferring control of an organization, or substantially all assets of one, or subdividing an organization, or merging organizations. If propagation of a covered work results from an entity transaction, each party to that transaction who receives a copy of the work also receives whatever licenses to the work the party's predecessor in interest had or could give under the previous paragraph, plus a right to possession of the Corresponding Source of the work from the predecessor in interest, if the predecessor has it or can get it with reasonable efforts. You may not impose any further restrictions on the exercise of the rights granted or affirmed under this License. For example, you may not impose a license fee, royalty, or other charge for exercise of rights granted under this License, and you may not initiate litigation (including a cross-claim or counterclaim in a lawsuit) alleging that any patent claim is infringed by making, using, selling, offering for sale, or importing the Program or any portion of it. 11. Patents. A “contributor” is a copyright holder who authorizes use under this License of the Program or a work on which the Program is based. The work thus licensed is called the contributor's “contributor version”. A contributor's “essential patent claims” are all patent claims owned or controlled by the contributor, whether already acquired or hereafter acquired, that would be infringed by some manner, permitted by this License, of making, using, or selling its contributor version, but do not include claims that would be infringed only as a consequence of further modification of the contributor version. For purposes of this definition, “control” includes the right to grant patent sublicenses in a manner consistent with the requirements of this License. Each contributor grants you a non-exclusive, worldwide, royalty-free patent license under the contributor's essential patent claims, to make, use, sell, offer for sale, import and otherwise run, modify and propagate the contents of its contributor version. In the following three paragraphs, a “patent license” is any express agreement or commitment, however denominated, not to enforce a patent (such as an express permission to practice a patent or covenant not to sue for patent infringement). To “grant” such a patent license to a party means to make such an agreement or commitment not to enforce a patent against the party. If you convey a covered work, knowingly relying on a patent license, and the Corresponding Source of the work is not available for anyone to copy, free of charge and under the terms of this License, through a publicly available network server or other readily accessible means, then you must either (1) cause the Corresponding Source to be so available, or (2) arrange to deprive yourself of the benefit of the patent license for this particular work, or (3) arrange, in a manner consistent with the requirements of this License, to extend the patent license to downstream recipients. “Knowingly relying” means you have actual knowledge that, but for the patent license, your conveying the covered work in a country, or your recipient's use of the covered work in a country, would infringe one or more identifiable patents in that country that you have reason to believe are valid. If, pursuant to or in connection with a single transaction or arrangement, you convey, or propagate by procuring conveyance of, a covered work, and grant a patent license to some of the parties receiving the covered work authorizing them to use, propagate, modify or convey a specific copy of the covered work, then the patent license you grant is automatically extended to all recipients of the covered work and works based on it. A patent license is “discriminatory” if it does not include within the scope of its coverage, prohibits the exercise of, or is conditioned on the non-exercise of one or more of the rights that are specifically granted under this License. You may not convey a covered work if you are a party to an arrangement with a third party that is in the business of distributing software, under which you make payment to the third party based on the extent of your activity of conveying the work, and under which the third party grants, to any of the parties who would receive the covered work from you, a discriminatory patent license (a) in connection with copies of the covered work conveyed by you (or copies made from those copies), or (b) primarily for and in connection with specific products or compilations that contain the covered work, unless you entered into that arrangement, or that patent license was granted, prior to 28 March 2007. Nothing in this License shall be construed as excluding or limiting any implied license or other defenses to infringement that may otherwise be available to you under applicable patent law. 12. No Surrender of Others' Freedom. If conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not excuse you from the conditions of this License. If you cannot convey a covered work so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not convey it at all. For example, if you agree to terms that obligate you to collect a royalty for further conveying from those to whom you convey the Program, the only way you could satisfy both those terms and this License would be to refrain entirely from conveying the Program. 13. Use with the GNU Affero General Public License. Notwithstanding any other provision of this License, you have permission to link or combine any covered work with a work licensed under version 3 of the GNU Affero General Public License into a single combined work, and to convey the resulting work. The terms of this License will continue to apply to the part which is the covered work, but the special requirements of the GNU Affero General Public License, section 13, concerning interaction through a network will apply to the combination as such. 14. Revised Versions of this License. The Free Software Foundation may publish revised and/or new versions of the GNU General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. Each version is given a distinguishing version number. If the Program specifies that a certain numbered version of the GNU General Public License “or any later version” applies to it, you have the option of following the terms and conditions either of that numbered version or of any later version published by the Free Software Foundation. If the Program does not specify a version number of the GNU General Public License, you may choose any version ever published by the Free Software Foundation. If the Program specifies that a proxy can decide which future versions of the GNU General Public License can be used, that proxy's public statement of acceptance of a version permanently authorizes you to choose that version for the Program. Later license versions may give you additional or different permissions. However, no additional obligations are imposed on any author or copyright holder as a result of your choosing to follow a later version. 15. Disclaimer of Warranty. THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM “AS IS” WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 16. Limitation of Liability. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. 17. Interpretation of Sections 15 and 16. If the disclaimer of warranty and limitation of liability provided above cannot be given local legal effect according to their terms, reviewing courts shall apply local law that most closely approximates an absolute waiver of all civil liability in connection with the Program, unless a warranty or assumption of liability accompanies a copy of the Program in return for a fee. END OF TERMS AND CONDITIONS malt-0.5.2/README.md000066400000000000000000000000441400455127600137340ustar00rootroot00000000000000# malt MALT - MEGAN alignment tool malt-0.5.2/antbuild/000077500000000000000000000000001400455127600142615ustar00rootroot00000000000000malt-0.5.2/antbuild/build.xml000066400000000000000000000056701400455127600161120ustar00rootroot00000000000000 malt-0.5.2/installer/000077500000000000000000000000001400455127600144545ustar00rootroot00000000000000malt-0.5.2/installer/License.txt000066400000000000000000000014071400455127600166010ustar00rootroot00000000000000MALT - MEGAN ALignment Tool Copyright (C) 2019, Daniel H. Huson This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . For more info on this program, see . malt-0.5.2/installer/malt.install4j000066400000000000000000002102171400455127600172420ustar00rootroot00000000000000 jna-* swt-* sqlite-* Daniel Huson's lab, University of Tübingen context.getBooleanVariable("sys.confirmedUpdateInstallation") ${form:welcomeMessage} !context.isConsole() String message = context.getMessage("ConsoleWelcomeLabel", context.getApplicationName()); return console.askOkCancel(message, true); updateCheck ${i18n:ClickNext} ${i18n:LicenseLabel3} en ./License.txt textSource displayedText displayedTextFile variableName acceptInitiallySelected readAllRequired !context.getBooleanVariable("sys.confirmedUpdateInstallation") ${i18n:SelectDirLabel(${compiler:sys.fullName})} suggestAppDir validateApplicationId existingDirWarning checkWritable manualEntryAllowed checkFreeSpace showRequiredDiskSpace showFreeDiskSpace allowSpacesOnUnix validationScript standardValidation ${i18n:SelectComponentsLabel2} !context.isConsole() selectionChangedScript ${compiler:sys.fullName} !context.getBooleanVariable("sys.confirmedUpdateInstallation") ${i18n:SelectAssociationsLabel} showSelectionButtons selectionButtonPosition ${i18n:UninstallerMenuEntry(${compiler:sys.fullName})} !context.getBooleanVariable("sys.programGroupDisabled") ${compiler:sys.fullName} ${compiler:sys.version} ${i18n:WizardPreparing} Check for updates how often? MALT Update Scheduler Set maximum allowed memory usage for MALT Set MALT memory 1691242235 -Xmx${installer:myXmx}G 1691243093 -Xmx${installer:myXmx}G 1691243204 -Xmx${installer:myXmx}G 1691243207 -Xmx${installer:myXmx}G Set maximum amount of memory that MALT can use. Set max memory usage (in gigabytes) myXmx ${form:finishedMessage} ${i18n:UninstallerMenuEntry(${compiler:sys.fullName})} Daniel Huson's lab, University of Tübingen ${form:welcomeMessage} !context.isConsole() String message = context.getMessage("ConfirmUninstall", context.getApplicationName()); return console.askYesNo(message, true); ${i18n:UninstallerPreparing} ${form:successMessage} ${compiler:sys.install4jHome}/resource/updater_16.png ${compiler:sys.install4jHome}/resource/updater_32.png ${compiler:sys.install4jHome}/resource/updater_48.png automaticUpdater ${i18n:updater.WindowTitle("${compiler:sys.fullName}")} Daniel Huson's lab, University of Tübingen ${compiler:sys.updatesUrl} updateDescriptor ((UpdateDescriptor)context.getVariable("updateDescriptor")).getPossibleUpdateEntry() updateDescriptorEntry context.getVariable("updateDescriptorEntry") != null ((UpdateDescriptorEntry)context.getVariable("updateDescriptorEntry")).getNewVersion() updaterNewVersion ((UpdateDescriptorEntry)context.getVariable("updateDescriptorEntry")).getFileSizeVerbose() updaterDownloadSize ((UpdateDescriptorEntry)context.getVariable("updateDescriptorEntry")).getComment() updaterComment Util.getUserHome() updaterDownloadDir ((UpdateDescriptorEntry)context.getVariable("updateDescriptorEntry")).getURL().toExternalForm() updaterDownloadUrl ((UpdateDescriptorEntry)context.getVariable("updateDescriptorEntry")).isArchive() ? Boolean.TRUE : Boolean.FALSE isArchive context.getVariable("updateDescriptorEntry") != null ${i18n:updater.NewVersionAvailableSubtitle("${compiler:sys.fullName}")} ${i18n:updater.NewVersionAvailableTitle} ${i18n:updater.CurrentVersionLabel} 128 0 0 255 dialog 1 0 ${installer:sys.version} ${i18n:updater.NewVersionLabel} 0 128 0 255 dialog 1 0 ${installer:updaterNewVersion} context.goForward(1, false, false); ${i18n:updater.ShowComments} ((String)context.getVariable("updaterComment")).length() > 0 ${i18n:updater.DownloadLocationLabel} ${installer:updaterDownloadDir} ${i18n:updater.DownloadToLabel} updaterDownloadLocation ${i18n:updater.DownloadSizeLabel} ${installer:updaterDownloadSize} ${i18n:updater.CommentsSubTitle} ${i18n:updater.CommentsTitle} false // This screen is only shown if the user clicks the "Show comments" hyperlink label in the previous screen. if (context.isConsole()) { context.goBackInHistory(1); } return true; WizardContext wizardContext = context.getWizardContext(); wizardContext.setNextButtonVisible(false); wizardContext.setCancelButtonVisible(false); ${i18n:updater.CommentsLabel} !context.isConsole() labelText ${installer:updaterComment} textSource displayedText displayedTextFile variableName console.waitForEnter(); return true; ${i18n:updater.DownloadSubTitle} ${i18n:updater.DownloadTitle} context.getWizardContext().setControlButtonVisible(ControlButtonType.NEXT, false); context.getWizardContext().setControlButtonVisible(ControlButtonType.PREVIOUS, false); context.goForward(1, true, true); context.getVariable("updaterDownloadLocation") + File.separator + ((UpdateDescriptorEntry)context.getVariable("updateDescriptorEntry")).getFileName() updaterDownloadFile ${installer:updaterDownloadFile} ${installer:updaterDownloadUrl} ${installer:updaterDownloadFile} 755 statusVisible initialStatusMessage ${i18n:updater.FinishTitle} ((Integer)context.getVariable("updaterLaunchSelection")).intValue() == 0 && !context.getBooleanVariable("isArchive") if (context.isUnattended()) { return new String[] {"-q", "-wait", "20"}; } else if (context.isConsole()) { return "-c"; } else { return ""; } installerArguments ${installer:installerArguments} ${installer:updaterDownloadFile} ${installer:updaterDownloadLocation} ${i18n:updater.FinishInfoText("${compiler:sys.fullName}")} !context.isConsole() labelText ${i18n:updater.LaunchUpdaterQuestion} ${i18n:updater.LaunchUpdaterLabel} ${i18n:updater.DoNotLaunchUpdaterLabel} updaterLaunchSelection !context.getBooleanVariable("isArchive") Util.showPath((String)context.getVariable("updaterDownloadFile")); ${i18n:updater.OpenContainingFolderLabel} !context.isConsole() malt-0.5.2/installer/malt2.install4j000066400000000000000000003021561400455127600173300ustar00rootroot00000000000000 -XX:NewRatio=2 -Xmx${installer:myXmx} -XX:NewRatio=2 -Xmx${installer:myXmx} -XX:NewRatio=2 -Xmx${installer:myXmx} -XX:NewRatio=2 -Xmx${installer:myXmx} -XX:NewRatio=2 -Xmx${installer:myXmx} -XX:NewRatio=2 -Xmx${installer:myXmx} -XX:NewRatio=2 -Xmx${installer:myXmx} -XX:NewRatio=2 -Xmx${installer:myXmx} Daniel Huson's lab, University of Tübingen false context.getBooleanVariable("sys.confirmedUpdateInstallation") en ./License.txt !context.getBooleanVariable("sys.confirmedUpdateInstallation") ${compiler:sys.fullName} !context.getBooleanVariable("sys.confirmedUpdateInstallation") ${i18n:UninstallerMenuEntry(${compiler:sys.fullName})} !context.getBooleanVariable("sys.programGroupDisabled") ${compiler:sys.fullName} ${compiler:sys.version} Check for updates how often? MALT Update Scheduler Set maximum allowed memory usage for MALT Set MALT memory !context.getMediaName().toLowerCase().contains("windows") || context.getMediaName().toLowerCase().contains("x64") || context.getMediaName().toLowerCase().contains("64bits") 1691242235 -Xmx${installer:myXmx}M Set maximum amount of memory that MALT can use. 64000 Set max memory usage (in megabytes) 512000 4000 1000 myXmx Daniel Huson's lab, University of Tübingen false ${compiler:sys.install4jHome}/resource/updater_16.png ${compiler:sys.install4jHome}/resource/updater_32.png ${compiler:sys.install4jHome}/resource/updater_48.png Daniel Huson's lab, University of Tübingen automaticUpdater true ${i18n:updater.WindowTitle("${compiler:sys.fullName}")} false ${compiler:sys.updatesUrl} updateDescriptor true ((UpdateDescriptor)context.getVariable("updateDescriptor")).getPossibleUpdateEntry() updateDescriptorEntry context.getVariable("updateDescriptorEntry") != null ((UpdateDescriptorEntry)context.getVariable("updateDescriptorEntry")).getNewVersion() updaterNewVersion ((UpdateDescriptorEntry)context.getVariable("updateDescriptorEntry")).getFileSizeVerbose() updaterDownloadSize ((UpdateDescriptorEntry)context.getVariable("updateDescriptorEntry")).getComment() updaterComment Util.getUserHome() updaterDownloadDir ((UpdateDescriptorEntry)context.getVariable("updateDescriptorEntry")).getURL().toExternalForm() updaterDownloadUrl ((UpdateDescriptorEntry)context.getVariable("updateDescriptorEntry")).isArchive() ? Boolean.TRUE : Boolean.FALSE isArchive context.getVariable("updateDescriptorEntry") != null ${i18n:updater.NewVersionAvailableSubtitle("${compiler:sys.fullName}")} ${i18n:updater.NewVersionAvailableTitle} ${i18n:updater.CurrentVersionLabel} 128 0 0 255 dialog 1 0 ${installer:sys.version} ${i18n:updater.NewVersionLabel} 0 128 0 255 dialog 1 0 ${installer:updaterNewVersion} context.goForward(1, false, false); ${i18n:updater.ShowComments} ((String)context.getVariable("updaterComment")).length() > 0 ${i18n:updater.DownloadLocationLabel} ${installer:updaterDownloadDir} ${i18n:updater.DownloadToLabel} false updaterDownloadLocation ${i18n:updater.DownloadSizeLabel} ${installer:updaterDownloadSize} ${installer:updaterComment} ${i18n:updater.CommentsLabel} ${i18n:updater.CommentsSubTitle} ${i18n:updater.CommentsTitle} false // This screen is only shown if the user clicks the "Show comments" hyperlink label in the previous screen. if (context.isConsole()) { context.goBackInHistory(1); } return true; WizardContext wizardContext = context.getWizardContext(); wizardContext.setNextButtonVisible(false); wizardContext.setCancelButtonVisible(false); ${i18n:updater.DownloadSubTitle} ${i18n:updater.DownloadTitle} context.getVariable("updaterDownloadLocation") + File.separator + ((UpdateDescriptorEntry)context.getVariable("updateDescriptorEntry")).getFileName() updaterDownloadFile ${installer:updaterDownloadFile} ${installer:updaterDownloadUrl} ${installer:updaterDownloadFile} 755 ${i18n:updater.FinishInfoText("${compiler:sys.fullName}")} ${i18n:updater.FinishTitle} ((Integer)context.getVariable("updaterLaunchSelection")).intValue() == 0 && !context.getBooleanVariable("isArchive") if (context.isUnattended()) { return new String[] {"-q", "-wait", "20"}; } else if (context.isConsole()) { return "-c"; } else { return ""; } installerArguments ${installer:installerArguments} ${installer:updaterDownloadFile} ${installer:updaterDownloadLocation} ${i18n:updater.LaunchUpdaterQuestion} 5 ${i18n:updater.LaunchUpdaterLabel} ${i18n:updater.DoNotLaunchUpdaterLabel} updaterLaunchSelection !context.getBooleanVariable("isArchive") Util.showPath((String)context.getVariable("updaterDownloadFile")); ${i18n:updater.OpenContainingFolderLabel} !context.isConsole() false true malt-0.5.2/resources/000077500000000000000000000000001400455127600144715ustar00rootroot00000000000000malt-0.5.2/resources/log4j.properties000066400000000000000000000050011400455127600176220ustar00rootroot00000000000000# # log4j.properties Copyright (C) 2020. Daniel H. Huson # # (Some files contain contributions from other authors, who are then mentioned separately.) # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . # # log4j.rootCategory=FATAL, S log4j.logger.com.dappit.Dapper.parser=FATAL log4j.logger.org.w3c.tidy=FATAL #------------------------------------------------------------------------------ # # The following properties configure the console (stdout) appender. # See http://logging.apache.org/log4j/docs/api/index.html for details. # #------------------------------------------------------------------------------ log4j.appender.S = org.apache.log4j.ConsoleAppender log4j.appender.S.layout = org.apache.log4j.PatternLayout log4j.appender.S.layout.ConversionPattern = %d{yyyy-MM-dd HH:mm:ss} %c{1} [%p] %m%n #------------------------------------------------------------------------------ # # The following properties configure the Daily Rolling File appender. # See http://logging.apache.org/log4j/docs/api/index.html for details. # #------------------------------------------------------------------------------ log4j.appender.R = org.apache.log4j.DailyRollingFileAppender log4j.appender.R.File = logs/bensApps.log log4j.appender.R.Append = true log4j.appender.R.DatePattern = '.'yyy-MM-dd log4j.appender.R.layout = org.apache.log4j.PatternLayout log4j.appender.R.layout.ConversionPattern = %d{yyyy-MM-dd HH:mm:ss} %c{1} [%p] %m%n #------------------------------------------------------------------------------ # # The following properties configure the Rolling File appender in HTML. # See http://logging.apache.org/log4j/docs/api/index.html for details. # #------------------------------------------------------------------------------ log4j.appender.H = org.apache.log4j.RollingFileAppender log4j.appender.H.File = logs/bensApps.html log4j.appender.H.MaxFileSize = 100KB log4j.appender.H.Append = false log4j.appender.H.layout = org.apache.log4j.HTMLLayout malt-0.5.2/src/000077500000000000000000000000001400455127600132465ustar00rootroot00000000000000malt-0.5.2/src/malt/000077500000000000000000000000001400455127600142035ustar00rootroot00000000000000malt-0.5.2/src/malt/AlignmentEngine.java000066400000000000000000000773241400455127600201270ustar00rootroot00000000000000/* * AlignmentEngine.java Copyright (C) 2020. Daniel H. Huson * * (Some files contain contributions from other authors, who are then mentioned separately.) * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * */ package malt; import jloda.util.Basic; import jloda.util.BlastMode; import malt.align.AlignerOptions; import malt.align.BandedAligner; import malt.data.*; import malt.io.*; import malt.util.FixedSizePriorityQueue; import malt.util.Utilities; import megan.genes.GeneItemAccessor; import java.io.IOException; import java.util.Arrays; import java.util.BitSet; import java.util.HashMap; import java.util.Map; /** * the main alignment engine. This runs in its own thread. It grabs the next read from the read queue and writes * the output to the ranked output writer * Daniel Huson, 8.2014 */ public class AlignmentEngine { private final int threadNumber; // used for output queuing // general data structures: private final MaltOptions maltOptions; private final ReferencesDBAccess referencesDB; private final ReferencesHashTableAccess[] tables; private final SeedShape[] seedShapes; private final int shift; // io: private final FastAReader fastAReader; private final MaltOptions.MatchOutputFormat matchOutputFormat; private final FileWriterRanked matchesWriter; private final FileWriterRanked alignedReadsWriter; private final FileWriterRanked unalignedReadsWriter; private final RMA6Writer rmaWriter; private final GeneItemAccessor geneTableAccess; // parameters private final double minRawScore; private final double minBitScore; private final double maxExpected; private final double percentIdentity; // xdrop heuristic: private final int xDrop; private final int minUngappedRawScore; // keep track of all used references: private final BitSet alignedReferenceIds; // used for stats: private long countSequencesProcessed; private long countSequencesWithAlignments; private long countSeedMatches; private long countHashSeedMismatches; private long countAlignments; // used in inner loop: private final FixedSizePriorityQueue matchesQueue; private final ReadMatch[] recycledMatchesArray; private final BandedAligner aligner; private final Map refIndex2ASeedMatches; private final ReadMatch[] readMatchesForRefIndex; private SeedMatchArray[] seedArrays; // used in inner loop to keep track of seed matches per reference sequence private int seedArraysLength = 0; static private QuerySequence2MatchesCache querySequence2MatchesCache = null; /** * construct an instance of the alignment engine. Each instance is run in a separate thread */ AlignmentEngine(final int threadNumber, final MaltOptions maltOptions, AlignerOptions alignerOptions, final ReferencesDBAccess referencesDB, final ReferencesHashTableAccess[] tables, final FastAReader fastAReader, final FileWriterRanked matchesWriter, final RMA6Writer rmaWriter, final FileWriterRanked alignedReadsWriter, final FileWriterRanked unalignedReadsWriter, final GeneItemAccessor geneTableAccess) throws IOException { this.threadNumber = threadNumber; this.maltOptions = maltOptions; this.referencesDB = referencesDB; this.tables = tables; this.fastAReader = fastAReader; this.matchOutputFormat = maltOptions.getMatchOutputFormat(); this.matchesWriter = matchesWriter; this.rmaWriter = rmaWriter; this.alignedReadsWriter = alignedReadsWriter; this.unalignedReadsWriter = unalignedReadsWriter; this.geneTableAccess = geneTableAccess; this.shift = maltOptions.getShift(); this.alignedReferenceIds = (maltOptions.isSparseSAM() ? null : new BitSet()); seedShapes = new SeedShape[tables.length]; for (int t = 0; t < tables.length; t++) { seedShapes[t] = tables[t].getSeedShape(); } // aligner and parameters aligner = new BandedAligner(alignerOptions, maltOptions.getMode()); minRawScore = aligner.getRawScoreForBitScore(maltOptions.getMinBitScore()); minBitScore = maltOptions.getMinBitScore(); maxExpected = maltOptions.getMaxExpected(); percentIdentity = maltOptions.getMinProportionIdentity(); // ungapped alignment parameters: xDrop = alignerOptions.getUngappedXDrop(maltOptions.getMode()); minUngappedRawScore = alignerOptions.getUngappedMinRawScore(maltOptions.getMode()); // data structures used in inner loop: matchesQueue = new FixedSizePriorityQueue<>(maltOptions.getMaxAlignmentsPerQuery(), ReadMatch.createComparator()); recycledMatchesArray = new ReadMatch[maltOptions.getMaxAlignmentsPerQuery()]; refIndex2ASeedMatches = new HashMap<>(10000, 0.9f); readMatchesForRefIndex = new ReadMatch[maltOptions.getMaxAlignmentsPerReference()]; for (int i = 0; i < readMatchesForRefIndex.length; i++) readMatchesForRefIndex[i] = new ReadMatch(); seedArrays = resizeAndConstructEntries(new SeedMatchArray[0], 1000, maltOptions.getMaxSeedsPerReference()); } /** * The main outer loop. Grabs the next input read and determines all possible seed matches. Then calls the inner loop */ void runOuterLoop() { try { final int maxFramesPerQuery = Utilities.getMaxFramesPerQuery(maltOptions.getMode(), maltOptions.isDoForward(), maltOptions.isDoReverse()); // setup thread specific data-structure: final DataForInnerLoop dataForInnerLoop = new DataForInnerLoop(maltOptions.getMode(), maltOptions.isDoForward(), maltOptions.isDoReverse(), maxFramesPerQuery, tables.length); // setup buffers for seeds. final byte[][][] seedBytes = new byte[maxFramesPerQuery][tables.length][]; for (int s = 0; s < maxFramesPerQuery; s++) { for (int t = 0; t < tables.length; t++) { seedBytes[s][t] = seedShapes[t].createBuffer(); // shape-specific buffer } } // iterate over all available queries, this method is thread-safe final FastARecord query = FastAReader.createFastARecord(1024, isWantQualityValues()); while (fastAReader.readAsFastA(query)) { if (querySequence2MatchesCache != null && querySequence2MatchesCache.contains(query.getSequence(), query.getSequenceLength())) { runInnerLoop(query, 0, null); // query is cached, no need to compute frames etc } else { // determine all frames to use: dataForInnerLoop.computeFrames(query.getSequence(), query.getQualityValues(), query.getSequenceLength()); // find seed matches for all frames and using all seed tables: int totalSize = 0; for (int s = 0; s < dataForInnerLoop.numberOfFrames; s++) { // for each frame of query for (int t = 0; t < tables.length; t++) { // consider each seed table final ReferencesHashTableAccess table = tables[t]; final SeedShape seedShape = table.getSeedShape(); int top = dataForInnerLoop.frameSequenceLength[s] - seedShape.getLength() + 1; for (int qOffset = 0; qOffset < dataForInnerLoop.frameSequenceLength[s]; qOffset += shift) { // consider all offsets if (qOffset < top) { final byte[] seed = seedShape.getSeed(dataForInnerLoop.frameSequence[s], qOffset, seedBytes[s][t]); totalSize += table.lookup(seed, dataForInnerLoop.frameXTableXSeed2Reference[s][t][qOffset]); } else dataForInnerLoop.frameXTableXSeed2Reference[s][t][qOffset].setEmpty(); } } } // run the inner loop runInnerLoop(query, totalSize, dataForInnerLoop); } } } catch (Exception ex) { Basic.caught(ex); System.exit(1); // just die... } } /** * run the inner loop. This tries to extend all found seed matches. If caching is used, first tries to find alignments in cache */ private void runInnerLoop(final FastARecord query, final int totalSize, final DataForInnerLoop dataForInnerLoop) throws IOException { countSequencesProcessed++; // if cache active and query found, use the cached matches: ReadMatch[] matchesArray = (querySequence2MatchesCache != null ? querySequence2MatchesCache.get(query.getSequence(), query.getSequenceLength()) : null); int numberOfMatches = (matchesArray != null ? matchesArray.length : 0); if (matchesArray != null) // found is cache, rescan counts { if (numberOfMatches > 0) { countAlignments += numberOfMatches; countSequencesWithAlignments++; } } else // not found in cache, need to compute... { if (totalSize > 0) { // have some seeds to look at try { // key a list of seed arrays that we reuse and reset here: if (seedArraysLength > 0) { for (int i = 0; i < seedArraysLength; i++) { seedArrays[i].clear(); } seedArraysLength = 0; } // determine all the seeds to be used, map each ref-index to its seeds, seeds know which frame of the query was used for (int s = 0; s < dataForInnerLoop.numberOfFrames; s++) { for (int t = 0; t < seedShapes.length; t++) { // for each seed-shape specific hash table for (int qOffset = 0; qOffset < dataForInnerLoop.frameSequenceLength[s]; qOffset += shift) { final Row matchLocations = dataForInnerLoop.frameXTableXSeed2Reference[s][t][qOffset]; // all locations of a particular seed int seedMatchesUsed = 0; for (int a = 0; a < matchLocations.size(); a += 2) { countSeedMatches++; final int refIndex = matchLocations.get(a); final int refOffset = matchLocations.get(a + 1); // todo: debugging if (refIndex >= referencesDB.getNumberOfSequences()) { System.err.println("matchLocations=" + matchLocations.toString()); throw new IOException("refIndex=" + refIndex + ": out of bounds: " + referencesDB.getNumberOfSequences()); } final byte[] referenceSequence = referencesDB.getSequence(refIndex); try { if (seedShapes[t].equalSequences(dataForInnerLoop.frameSequence[s], qOffset, referenceSequence, refOffset)) { if (seedMatchesUsed++ >= maltOptions.getMaxSeedsPerOffsetPerFrame()) { break; // exceeded the maximum number of seeds per frame } SeedMatchArray set = refIndex2ASeedMatches.get(refIndex); if (set == null) { if (seedArraysLength >= seedArrays.length) { //System.err.println("seedArray: " + seedArrays.length + " -> " + (2 * seedArraysLength)); seedArrays = resizeAndConstructEntries(seedArrays, 2 * seedArraysLength, maltOptions.getMaxSeedsPerReference()); } set = seedArrays[seedArraysLength++]; refIndex2ASeedMatches.put(refIndex, set); } if (set.size() < maltOptions.getMaxSeedsPerReference()) { set.setNext(qOffset, refOffset, s, seedShapes[t].getLength()); // else System.err.println("SKIPPED"); } } else countHashSeedMismatches++; } catch (Exception ex) { Basic.caught(ex); } } } } } // try to align each seed for (Integer refIndex : refIndex2ASeedMatches.keySet()) { SeedMatch previous = null; final SeedMatchArray seedMatches = refIndex2ASeedMatches.get(refIndex); seedMatches.sort(); int numberOfReadMatchesForRefIndex = 0; // we keep a short array of best hits for the given reference index for (int i = 0; i < seedMatches.size(); i++) { SeedMatch seedMatch = seedMatches.get(i); if (!seedMatch.follows(previous)) { // ignore back-to-back matches // todo: debugging if (refIndex >= referencesDB.getNumberOfSequences()) { System.err.println("seedMatch=" + seedMatch.toString()); throw new IOException("refIndex=" + refIndex + ": out of bounds: " + referencesDB.getNumberOfSequences()); } final byte[] referenceSequence = referencesDB.getSequence(refIndex); final byte[] sequence = dataForInnerLoop.frameSequence[seedMatch.getRank()]; int length = dataForInnerLoop.frameSequenceLength[seedMatch.getRank()]; if (aligner.quickCheck(sequence, length, referenceSequence, referenceSequence.length, seedMatch.getQueryOffset(), seedMatch.getReferenceOffset())) { aligner.computeAlignment(sequence, length, referenceSequence, referenceSequence.length, seedMatch.getQueryOffset(), seedMatch.getReferenceOffset(), seedMatch.getSeedLength()); if (aligner.getRawScore() >= minRawScore) { // have found match with sufficient rawScore // compute bitscore and expected score aligner.computeBitScoreAndExpected(); if (aligner.getBitScore() >= minBitScore && aligner.getExpected() <= maxExpected) { ReadMatch readMatch; boolean foundPlaceToKeepThisMatch; boolean incrementedNumberOfReadMatchesForRefIndex = false; if (readMatchesForRefIndex.length == 1) { // only allowing one hit per reference... readMatch = readMatchesForRefIndex[0]; numberOfReadMatchesForRefIndex = 1; foundPlaceToKeepThisMatch = true; incrementedNumberOfReadMatchesForRefIndex = true; } else { //allow more than one hit // ensure that this match does not overlap an existing match of same or better quality boolean overlap = false; for (int z = 0; z < numberOfReadMatchesForRefIndex; z++) { readMatch = readMatchesForRefIndex[z]; if (readMatch.getBitScore() >= aligner.getBitScore() && readMatch.overlap(aligner.getStartReference(), aligner.getEndReference())) { overlap = true; break; } } if (overlap) continue; // keep this match, if array not full: if (numberOfReadMatchesForRefIndex < readMatchesForRefIndex.length) { readMatch = readMatchesForRefIndex[numberOfReadMatchesForRefIndex++]; foundPlaceToKeepThisMatch = true; incrementedNumberOfReadMatchesForRefIndex = true; } else { // otherwise replace one with lower rawScore foundPlaceToKeepThisMatch = false; readMatch = null; for (int z = 0; z < numberOfReadMatchesForRefIndex; z++) { readMatch = readMatchesForRefIndex[z]; if (aligner.getBitScore() > readMatch.getBitScore()) { foundPlaceToKeepThisMatch = true; break; } } } } if (foundPlaceToKeepThisMatch) { final byte[] referenceHeader; if (geneTableAccess == null) referenceHeader = referencesDB.getHeader(refIndex); else { int start = aligner.getStartReference(); if (start == -1) { aligner.computeAlignmentByTraceBack(); start = aligner.getStartReference(); } int end = aligner.getEndReference(); referenceHeader = geneTableAccess.annotateRefString(Basic.toString(referencesDB.getHeader(refIndex)), refIndex, start, end).getBytes(); //System.err.println(Basic.toString(referenceHeader)); } byte[] text = null; byte[] rma6Text = null; if (matchesWriter != null) { switch (matchOutputFormat) { default: case Text: { text = aligner.getAlignmentText(dataForInnerLoop, seedMatch.getRank()); break; } case Tab: { text = aligner.getAlignmentTab(dataForInnerLoop, null, referenceHeader, seedMatch.getRank()); // don't pass queryHeader, it is added below break; } case SAM: { rma6Text = text = aligner.getAlignmentSAM(dataForInnerLoop, null, query.getSequence(), referenceHeader, seedMatch.getRank()); // don't pass queryHeader, it is added below break; } } } if (rmaWriter != null && rma6Text == null) { rma6Text = aligner.getAlignmentSAM(dataForInnerLoop, null, query.getSequence(), referenceHeader, seedMatch.getRank()); // don't pass queryHeader, it is added below } if (percentIdentity > 0) // need to filter by percent identity. Can't do this earlier because number of matches not known until alignment has been computed { if (text == null && rma6Text == null) // haven't computed alignment, so number of matches not yet computed aligner.computeAlignmentByTraceBack(); // compute number of matches if (aligner.getIdentities() < percentIdentity * aligner.getAlignmentLength()) { // too few identities if (incrementedNumberOfReadMatchesForRefIndex) numberOfReadMatchesForRefIndex--; // undo increment, won't be saving this match continue; } } readMatch.set(aligner.getBitScore(), refIndex, text, rma6Text, aligner.getStartReference(), aligner.getEndReference()); } previous = seedMatch; } } } } } for (int z = 0; z < numberOfReadMatchesForRefIndex; z++) { matchesQueue.add(readMatchesForRefIndex[z].getCopy()); } } } finally { // erase the seed sets refIndex2ASeedMatches.clear(); } } if (matchesQueue.size() > 0) { countAlignments += matchesQueue.size(); countSequencesWithAlignments++; numberOfMatches = matchesQueue.size(); for (int i = numberOfMatches - 1; i >= 0; i--) { // places matches into array ordered by descending score recycledMatchesArray[i] = matchesQueue.poll(); } matchesArray = recycledMatchesArray; // we reuse the matches array in the case that we are not using matches cache } // if use caching, save, even if no matches found! if (querySequence2MatchesCache != null) { querySequence2MatchesCache.put(query.getSequence(), query.getSequenceLength(), matchesArray, numberOfMatches); // ok to pass matchesArray==null when numberOfMatches==0 } } // output the alignments or skip the read (or output on skip, if negative filter...): if (numberOfMatches > 0) { if (matchesWriter != null) { switch (matchOutputFormat) { default: case Text: { byte[][] strings = new byte[3 * numberOfMatches + 1][]; strings[0] = BlastTextHelper.makeQueryLine(query); for (int i = 0; i < numberOfMatches; i++) { final ReadMatch readMatch = matchesArray[i]; strings[3 * i + 1] = referencesDB.getHeader(readMatch.getReferenceId()); strings[3 * i + 2] = String.format("\tLength=%d\n", referencesDB.getSequenceLength(readMatch.getReferenceId())).getBytes(); strings[3 * i + 3] = readMatch.getText(); } matchesWriter.writeByRank(threadNumber, query.getId(), strings); break; } case SAM: case Tab: { byte[] queryNamePlusTab = BlastTextHelper.getQueryNamePlusTab(query); byte[][] strings = new byte[2 * numberOfMatches][]; for (int i = 0; i < numberOfMatches; i++) { ReadMatch readMatch = matchesArray[i]; strings[2 * i] = queryNamePlusTab; strings[2 * i + 1] = readMatch.getText(); } matchesWriter.writeByRank(threadNumber, query.getId(), strings); break; } } } if (rmaWriter != null) { rmaWriter.processMatches(query.getHeaderString(), query.getSequenceString(), matchesArray, numberOfMatches); } if (alignedReferenceIds != null) { for (int i = 0; i < numberOfMatches; i++) { final ReadMatch readMatch = matchesArray[i]; alignedReferenceIds.set(readMatch.getReferenceId()); } } if (alignedReadsWriter != null) { alignedReadsWriter.writeByRank(threadNumber, query.getId(), Utilities.getFirstWordEnsureLeadingGreaterSign(query.getHeader()), Utilities.copy0Terminated(query.getSequence())); } if (unalignedReadsWriter != null) { unalignedReadsWriter.skipByRank(threadNumber, query.getId()); } // matchesQueue.erase(); // not necessary because queue is consumed when building array } else { // no match if (matchesWriter != null) { if (matchOutputFormat == MaltOptions.MatchOutputFormat.Text) { // report no-hits statement matchesWriter.writeByRank(threadNumber, query.getId(), BlastTextHelper.makeQueryLine(query), BlastTextHelper.NO_HITS); } else { matchesWriter.skipByRank(threadNumber, query.getId()); } } if (rmaWriter != null && maltOptions.isSaveUnalignedToRMA()) { rmaWriter.processMatches(query.getHeaderString(), query.getSequenceString(), matchesArray, 0); } if (alignedReadsWriter != null) { alignedReadsWriter.skipByRank(threadNumber, query.getId()); } if (unalignedReadsWriter != null) { unalignedReadsWriter.writeByRank(threadNumber, query.getId(), Utilities.getFirstWordEnsureLeadingGreaterSign(query.getHeader()), Utilities.copy0Terminated(query.getSequence())); } } } /** * finish up after outer loop completed */ public void finish() { } /** * compute total sequences processed */ static long getTotalSequencesProcessed(final AlignmentEngine[] alignmentEngines) { long total = 0; for (AlignmentEngine alignmentEngine : alignmentEngines) { total += alignmentEngine.countSequencesProcessed; } return total; } /** * compute total with alignments */ static long getTotalSequencesWithAlignments(final AlignmentEngine[] alignmentEngines) { long total = 0; for (AlignmentEngine alignmentEngine : alignmentEngines) { total += alignmentEngine.countSequencesWithAlignments; } return total; } /** * compute total number of alignments */ static long getTotalAlignments(final AlignmentEngine[] alignmentEngines) { long total = 0; for (AlignmentEngine alignmentEngine : alignmentEngines) { total += alignmentEngine.countAlignments; } return total; } BitSet getAlignedReferenceIds() { return alignedReferenceIds; } /** * resize the array of seed match arrays */ private SeedMatchArray[] resizeAndConstructEntries(SeedMatchArray[] array, int newSize, int maxLength) { SeedMatchArray[] result = new SeedMatchArray[newSize]; for (int i = array.length; i < newSize; i++) result[i] = new SeedMatchArray(maxLength); System.arraycopy(array, 0, result, 0, Math.min(newSize, array.length)); return result; } /** * initialize the read sequence 2 matches cache */ static void activateReplicateQueryCaching(int bits) { System.err.println("Using replicate query cache (cache size=" + (1 << bits) + ")"); querySequence2MatchesCache = new QuerySequence2MatchesCache(bits); } /** * report on cache usage, if any */ static void reportStats() { if (querySequence2MatchesCache != null) querySequence2MatchesCache.reportStats(); } /** * do we want to collect and save quality values? * * @return true, if mode is BLASTN, output format is SAM and input file is fastQ */ private boolean isWantQualityValues() { return (maltOptions.getMode() == BlastMode.BlastN && maltOptions.getMatchOutputFormat() == MaltOptions.MatchOutputFormat.SAM && fastAReader.isFastQ()); } /** * an array of seed matches */ static class SeedMatchArray { int size; SeedMatch[] matches; SeedMatchArray(int length) { matches = SeedMatch.resizeAndConstructEntries(matches, length); } public int size() { return size; } public SeedMatch get(int i) { return matches[i]; } void setNext(int queryOffset, int referenceOffset, int rank, int seedLength) { matches[size++].set(queryOffset, referenceOffset, rank, seedLength); } public void clear() { size = 0; } public void sort() { Arrays.sort(matches, 0, size, SeedMatch.getComparator()); } } } malt-0.5.2/src/malt/DataForInnerLoop.java000066400000000000000000000175241400455127600202250ustar00rootroot00000000000000/* * DataForInnerLoop.java Copyright (C) 2020. Daniel H. Huson * * (Some files contain contributions from other authors, who are then mentioned separately.) * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * */ package malt; /** * this contains all query specific data that is passed to the inner loop of the algorithm * Daniel Huson, 8.2014 */ import jloda.util.BlastMode; import malt.data.DNA5; import malt.data.Row; import malt.data.Translator; import malt.util.Utilities; import java.io.IOException; /** * this contains all query specific data that is passed to the inner loop of the algorithm */ public class DataForInnerLoop { private final BlastMode mode; private final boolean doForward; private final boolean doReverse; final int maxNumberOfFrames; final int numberOfTables; final String[] positiveFrameInfoString; final String[] negativeFrameInfoString; private int maxQueryLength; private int queryLength; public int numberOfFrames; private final int[] frame; public final byte[][] frameSequence; public byte[] qualityValues; public final int[] frameSequenceLength; public final Row[][][] frameXTableXSeed2Reference; /** * constructor * * @param mode * @param maxNumberOfFrames * @param numberOfTables */ public DataForInnerLoop(BlastMode mode, boolean doForward, boolean doReverse, int maxNumberOfFrames, int numberOfTables) { this.mode = mode; this.doForward = doForward; this.doReverse = doReverse; this.maxNumberOfFrames = maxNumberOfFrames; this.numberOfTables = numberOfTables; maxQueryLength = 0; frame = new int[maxNumberOfFrames]; frameSequence = new byte[maxNumberOfFrames][maxQueryLength]; frameSequenceLength = new int[maxNumberOfFrames]; frameXTableXSeed2Reference = new Row[maxNumberOfFrames][numberOfTables][maxQueryLength]; // for BlastP and BlastN the frames never replace so we set them here once and for all: switch (mode) { default: case BlastP: { frame[0] = 1; numberOfFrames = 1; positiveFrameInfoString = new String[2]; // no frame info line for BlastP negativeFrameInfoString = null; break; } case BlastN: { int s = 0; if (doForward) { positiveFrameInfoString = new String[2]; positiveFrameInfoString[1] = " Strand = Plus / Plus\n"; frame[s] = 1; numberOfFrames++; s++; } else positiveFrameInfoString = null; if (doReverse) { negativeFrameInfoString = new String[2]; frame[s] = -1; negativeFrameInfoString[1] = " Strand = Minus / Plus\n"; numberOfFrames++; } else negativeFrameInfoString = null; break; } case BlastX: { positiveFrameInfoString = new String[4]; negativeFrameInfoString = new String[4]; for (int i = 1; i <= 3; i++) { positiveFrameInfoString[i] = " Frame = +" + i + "\n"; negativeFrameInfoString[i] = " Frame = -" + i + "\n"; } } } } /** * compute frames and resize data-structures if necessary * For BlastP there are no frames, for BlastN there can be up to two frames, +1 and -1, * for BlastX the max number is 6 * * @param query * @param queryLength * @throws java.io.IOException */ public void computeFrames(byte[] query, byte[] queryQualityValues, int queryLength) throws IOException { this.queryLength = queryLength; switch (mode) { case BlastN: { int s = 0; if (doForward) { frameSequence[s] = query; frameSequenceLength[s] = queryLength; s++; } if (doReverse) { if (maxQueryLength < queryLength) { frameSequence[s] = new byte[queryLength]; } DNA5.getInstance().getReverseComplement(query, queryLength, frameSequence[s]); frameSequenceLength[s] = queryLength; } qualityValues = queryQualityValues; break; } case BlastP: frameSequence[0] = query; frameSequenceLength[0] = queryLength; break; case BlastX: if (maxQueryLength < queryLength) // don't worry about dividing by 3 { for (int s = 0; s < maxNumberOfFrames; s++) frameSequence[s] = new byte[queryLength]; } numberOfFrames = Translator.getBestFrames(doForward, doReverse, query, queryLength, frame, frameSequence, frameSequenceLength); break; default: throw new IOException("Unsupported mode: " + mode); } // resize arrays: if (maxQueryLength < queryLength) { maxQueryLength = queryLength; for (int s = 0; s < maxNumberOfFrames; s++) { for (int t = 0; t < numberOfTables; t++) { frameXTableXSeed2Reference[s][t] = Utilities.resizeAndConstructEntries(frameXTableXSeed2Reference[s][t], maxQueryLength); } } } } public int getStartQueryForOutput(int frameRank, int startQuery) { switch (mode) { case BlastN: { if (frame[frameRank] == 1) return startQuery + 1; else return queryLength - startQuery; } case BlastX: { if (frame[frameRank] > 0) return 3 * startQuery + frame[frameRank]; else return queryLength - 3 * startQuery + frame[frameRank] + 1; } default: case BlastP: return startQuery + 1; } } public int getEndQueryForOutput(int frameRank, int endQuery) { switch (mode) { case BlastN: { if (frame[frameRank] == 1) return endQuery; else return queryLength - endQuery + 1; } case BlastX: { if (frame[frameRank] > 0) return 3 * endQuery + frame[frameRank] - 1; else return queryLength - 3 * endQuery + frame[frameRank] + 2; } default: case BlastP: return endQuery; } } public String getFrameInfoLine(int frameRank) { int f = frame[frameRank]; if (f > 0) return positiveFrameInfoString[f]; else return negativeFrameInfoString[-f]; } public int getFrameForFrameRank(int frameRank) { return frame[frameRank]; } public byte[] getQualityValues() { return qualityValues; } } malt-0.5.2/src/malt/ITextProducer.java000066400000000000000000000017221400455127600176110ustar00rootroot00000000000000/* * ITextProducer.java Copyright (C) 2020. Daniel H. Huson * * (Some files contain contributions from other authors, who are then mentioned separately.) * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * */ package malt; /** * a callback method that returns a text in bytes * Daniel Huson, 9.2014 */ public interface ITextProducer { byte[] getText(); } malt-0.5.2/src/malt/MaltBuild.java000066400000000000000000000425331400455127600167320ustar00rootroot00000000000000/* * MaltBuild.java Copyright (C) 2020. Daniel H. Huson * * (Some files contain contributions from other authors, who are then mentioned separately.) * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * */ package malt; import jloda.swing.util.ArgsOptions; import jloda.swing.util.BasicSwing; import jloda.swing.util.FastaFileFilter; import jloda.swing.util.ResourceManager; import jloda.util.*; import jloda.util.interval.Interval; import malt.data.*; import malt.mapping.Mapping; import malt.util.Utilities; import megan.accessiondb.AccessAccessionMappingDatabase; import megan.classification.Classification; import megan.classification.ClassificationManager; import megan.classification.IdMapper; import megan.classification.IdParser; import megan.genes.GeneItem; import megan.genes.GeneItemCreator; import megan.main.Megan6; import megan.tools.AAdderBuild; import java.io.File; import java.io.IOException; import java.util.*; import java.util.stream.Collectors; /** * build MALT index * Daniel Huson, 8.2014 */ public class MaltBuild { final public static String INDEX_CREATOR = "MALT"; /** * run the program * * @param args * @throws jloda.util.UsageException * @throws java.io.IOException */ public static void main(String[] args) { ResourceManager.addResourceRoot(Megan6.class, "megan.resources"); try { PeakMemoryUsageMonitor.start(); final MaltBuild maltBuild = new MaltBuild(); ResourceManager.setWarningMissingIcon(false); ProgramProperties.setProgramIcons(ResourceManager.getIcons("malt-build16.png", "malt-build32.png", "malt-build48.png")); ProgramProperties.setProgramName("MaltBuild"); ProgramProperties.setProgramVersion(Version.SHORT_DESCRIPTION); maltBuild.run(args); System.err.println("Total time: " + PeakMemoryUsageMonitor.getSecondsSinceStartString()); System.err.println("Peak memory: " + PeakMemoryUsageMonitor.getPeakUsageString()); if (!ArgsOptions.hasMessageWindow()) System.exit(0); else System.err.println("DONE - close window to quit"); } catch (Exception ex) { if (ex.getMessage() == null || !ex.getMessage().startsWith("Help")) Basic.caught(ex); if (!ArgsOptions.hasMessageWindow()) System.exit(1); else System.err.println("DONE - close window to quit"); } } /** * run the program * * @param args * @throws UsageException * @throws IOException */ public void run(String[] args) throws Exception { // parse commandline options: final ArgsOptions options = new ArgsOptions(args, this, "Builds an index for MALT (MEGAN alignment tool)"); options.setAuthors("Daniel H. Huson"); options.setVersion(ProgramProperties.getProgramVersion()); options.setLicense("Copyright (C) 2020 Daniel H. Huson. This program comes with ABSOLUTELY NO WARRANTY."); options.comment("Input:"); final List inputFiles = options.getOptionMandatory("i", "input", "Input reference files in FastA format (or specify a single directory)", new LinkedList<>()); final SequenceType sequenceType = SequenceType.valueOfIgnoreCase(options.getOptionMandatory("s", "sequenceType", "Sequence type", SequenceType.values(), SequenceType.Protein.toString())); final List gffFiles = options.getOption("-igff", "inputGFF", "Files that provide CDS annotations of DNA input files in GFF format (or specify a single directory)", new LinkedList<>()); options.comment("Output:"); final String indexDirectoryName = options.getOptionMandatory("-d", "index", "Name of index directory", ""); options.comment("Performance:"); final int numberOfThreads = options.getOption("-t", "threads", "Number of worker threads", Runtime.getRuntime().availableProcessors()); final int stepSize = options.getOption("-st", "step", "Step size used to advance seed; a value greater than 1 will reduce index size, but also sensitivity", 1, 1, 100); options.comment("Seed:"); String[] shapes = options.getOption("-ss", "shapes", "Seed shape(s)", new String[]{"default"}); int maxHitsPerSeed = options.getOption("-mh", "maxHitsPerSeed", "Maximum number of hits per seed", 1000); final String proteinReduction; if (sequenceType == SequenceType.Protein || options.isDoHelp()) proteinReduction = options.getOption("-pr", "proteinReduct", "Name or definition of protein alphabet reduction (" + Basic.toString(malt.data.ReducedAlphabet.reductions.keySet(), ",") + ")", "DIAMOND_11"); else proteinReduction = ""; options.comment("Classification support:"); final String mapDBFile = options.getOption("-mdb", "mapDB", "MEGAN mapping db (file megan-map.db or megan-nucl.db)", ""); final Set dbSelectedClassifications = new HashSet<>(Arrays.asList(options.getOption("-on", "only", "Use only named classifications (if not set: use all)", new String[0]))); options.comment("Deprecated classification support:"); final boolean parseTaxonNames = options.getOption("-tn", "parseTaxonNames", "Parse taxon names", true); final String acc2TaxaFile = options.getOption("-a2t", "acc2taxa", "Accession-to-Taxonomy mapping file", ""); final String synonyms2TaxaFile = options.getOption("-s2t", "syn2taxa", "Synonyms-to-Taxonomy mapping file", ""); final HashMap class2AccessionFile = new HashMap<>(); final HashMap class2SynonymsFile = new HashMap<>(); for (String cName : ClassificationManager.getAllSupportedClassificationsExcludingNCBITaxonomy()) { class2AccessionFile.put(cName, options.getOption("-a2" + cName.toLowerCase(), "acc2" + cName.toLowerCase(), "Accession-to-" + cName + " mapping file", "")); class2SynonymsFile.put(cName, options.getOption("-s2" + cName.toLowerCase(), "syn2" + cName.toLowerCase(), "Synonyms-to-" + cName + " mapping file", "")); final String tags = options.getOption("-t4" + cName.toLowerCase(), "tags4" + cName.toLowerCase(), "Tags for " + cName + " id parsing (must set to activate id parsing)", "").trim(); if (tags.length() > 0) ProgramProperties.put(cName + "Tags", tags); ProgramProperties.put(cName + "ParseIds", tags.length() > 0); } final boolean functionalClassification = !options.getOption("-nf", "noFun", "Turn off functional classifications for provided mapping files (set this when using GFF files for DNA references)", false); options.comment(ArgsOptions.OTHER); ProgramProperties.put(IdParser.PROPERTIES_FIRST_WORD_IS_ACCESSION, options.getOption("-fwa", "firstWordIsAccession", "First word in reference header is accession number", ProgramProperties.get(IdParser.PROPERTIES_FIRST_WORD_IS_ACCESSION, true))); ProgramProperties.put(IdParser.PROPERTIES_ACCESSION_TAGS, options.getOption("-atags", "accessionTags", "List of accession tags", ProgramProperties.get(IdParser.PROPERTIES_ACCESSION_TAGS, IdParser.ACCESSION_TAGS))); final boolean saveFirstWordOfReferenceHeaderOnly = options.getOption("-fwo", "firstWordOnly", "Save only first word of reference header", false); final int randomSeed = options.getOption("rns", "random", "Random number generator seed", 666); final float hashTableLoadFactor = options.getOption("hsf", "hashScaleFactor", "Hash table scale factor", 0.9f, 0.1f, 1.0f); final boolean buildTableInMemory = options.getOption("btm", "buildTableInMemory", "Build the hash table in memory and then save (uses more memory, is much faster)", true); final boolean doBuildTables = !options.getOption("!xX", "xSkipTable", "Don't recompute index and tables, just compute profile support", false); final boolean lookInside = options.getOption("-ex", "extraStrict", "When given an input directory, look inside every GFF file to check that it is indeed in GFF3 format", false); options.done(); Basic.setDebugMode(options.isVerbose()); if (sequenceType == null) throw new IOException("Sequence type undefined"); if (inputFiles.size() == 1) { final File file = new File(inputFiles.get(0)); if (file.isDirectory()) { System.err.println("Looking for FastA files in directory: " + file); inputFiles.clear(); for (File aFile : BasicSwing.getAllFilesInDirectory(file, new FastaFileFilter(), true)) { inputFiles.add(aFile.getPath()); } if (inputFiles.size() == 0) throw new IOException("No files found in directory: " + file); else System.err.printf("Found: %,d%n", inputFiles.size()); } } if (Basic.notBlank(mapDBFile)) Basic.checkFileReadableNonEmpty(mapDBFile); final Collection mapDBClassifications = AccessAccessionMappingDatabase.getContainedClassificationsIfDBExists(mapDBFile) .stream().filter(s -> dbSelectedClassifications.size() == 0 || dbSelectedClassifications.contains(s)).collect(Collectors.toList()); if (mapDBClassifications.size() > 0 && (Basic.hasPositiveLengthValue(class2AccessionFile) || Basic.hasPositiveLengthValue(class2SynonymsFile))) throw new UsageException("Illegal to use both --mapDB and ---acc2... or --syn2... options"); if (mapDBClassifications.size() > 0) ClassificationManager.setMeganMapDBFile(mapDBFile); final ArrayList cNames = new ArrayList<>(); if (mapDBClassifications.size() > 0) { cNames.addAll(mapDBClassifications); } else { for (String cName : ClassificationManager.getAllSupportedClassificationsExcludingNCBITaxonomy()) { if ((dbSelectedClassifications.size() == 0 || dbSelectedClassifications.contains(cName)) && (Basic.notBlank(class2AccessionFile.get(cName)) || Basic.notBlank(class2SynonymsFile.get(cName)))) cNames.add(cName); } if (!cNames.contains(Classification.Taxonomy) && (acc2TaxaFile.length() > 0 || synonyms2TaxaFile.length() > 0)) cNames.add(Classification.Taxonomy); } if (cNames.size() > 0) System.err.println("Classifications to use: " + Basic.toString(cNames, ", ")); AAdderBuild.setupGFFFiles(gffFiles, lookInside); System.err.println("Reference sequence type set to: " + sequenceType.toString()); final IAlphabet referenceAlphabet; final IAlphabet seedAlphabet; switch (sequenceType) { case DNA: if (shapes[0].equalsIgnoreCase("default")) { shapes = new String[]{SeedShape.SINGLE_DNA_SEED}; } referenceAlphabet = DNA5.getInstance(); seedAlphabet = DNA5.getInstance(); break; case Protein: if (shapes[0].equalsIgnoreCase("default")) { shapes = SeedShape.PROTEIN_SEEDS; } referenceAlphabet = ProteinAlphabet.getInstance(); seedAlphabet = new ReducedAlphabet(proteinReduction); break; default: throw new UsageException("Undefined sequence type: " + sequenceType); } System.err.println("Seed shape(s): " + Basic.toString(shapes, ", ")); final File indexDirectory = new File(indexDirectoryName); if (doBuildTables) { if (indexDirectory.exists()) { Utilities.cleanIndexDirectory(indexDirectory); } else { if (!indexDirectory.mkdir()) throw new IOException("mkdir failed: " + indexDirectoryName); } } else System.err.println("NOT BUILDING INDEX OR TABLES"); final File referenceFile = new File(indexDirectory, "ref.idx"); if ((!referenceFile.exists() || referenceFile.delete()) && !referenceFile.createNewFile()) throw new IOException("Can't create file: " + referenceFile); ReferencesHashTableBuilder.checkCanWriteFiles(indexDirectoryName, 0); // load the reference file: final ReferencesDBBuilder referencesDB = new ReferencesDBBuilder(); System.err.printf("Number input files: %,12d%n", inputFiles.size()); referencesDB.loadFastAFiles(inputFiles, referenceAlphabet); System.err.printf("Number of sequences:%,12d%n", referencesDB.getNumberOfSequences()); System.err.printf("Number of letters:%,14d%n", referencesDB.getNumberOfLetters()); // generate hash table for each seed shape if (doBuildTables) { for (int tableNumber = 0; tableNumber < shapes.length; tableNumber++) { final String shape = shapes[tableNumber]; final SeedShape seedShape = new SeedShape(seedAlphabet, shape); System.err.println("BUILDING table (" + tableNumber + ")..."); final ReferencesHashTableBuilder hashTable = new ReferencesHashTableBuilder(sequenceType, seedAlphabet, seedShape, referencesDB.getNumberOfSequences(), referencesDB.getNumberOfLetters(), randomSeed, maxHitsPerSeed, hashTableLoadFactor, stepSize); hashTable.buildTable(new File(indexDirectory, "table" + tableNumber + ".idx"), new File(indexDirectory, "table" + tableNumber + ".db"), referencesDB, numberOfThreads, buildTableInMemory); hashTable.saveIndexFile(new File(indexDirectory, "index" + tableNumber + ".idx")); } } for (String cName : cNames) { final String cNameLowerCase = cName.toLowerCase(); final String sourceName = (cName.equals(Classification.Taxonomy) ? "ncbi" : cNameLowerCase); ClassificationManager.ensureTreeIsLoaded(cName); Basic.writeStreamToFile(ResourceManager.getFileAsStream(sourceName + ".tre"), new File(indexDirectory, cNameLowerCase + ".tre")); Basic.writeStreamToFile(ResourceManager.getFileAsStream(sourceName + ".map"), new File(indexDirectory, cNameLowerCase + ".map")); } if (mapDBFile.length() == 0) { for (String cName : cNames) { final String cNameLowerCase = cName.toLowerCase(); if (mapDBClassifications.contains(cName)) Utilities.loadMapping(mapDBFile, IdMapper.MapType.MeganMapDB, cName); if (class2SynonymsFile.get(cName) != null) Utilities.loadMapping(class2SynonymsFile.get(cName), IdMapper.MapType.Synonyms, cName); if (class2AccessionFile.get(cName) != null) Utilities.loadMapping(class2AccessionFile.get(cName), IdMapper.MapType.Accession, cName); final IdParser idParser = ClassificationManager.get(cName, true).getIdMapper().createIdParser(); if (cName.equals(Classification.Taxonomy)) idParser.setUseTextParsing(parseTaxonNames); if (functionalClassification || cName.equals(Classification.Taxonomy)) { final Mapping mapping = Mapping.create(cName, referencesDB, idParser, new ProgressPercentage("Building " + cName + "-mapping...")); mapping.save(new File(indexDirectory, cNameLowerCase + ".idx")); } } } else { final Map mappings; try (var progress = new ProgressPercentage("Building mappings...")) { mappings = Mapping.create(cNames, referencesDB, new AccessAccessionMappingDatabase(mapDBFile), progress); } for (String cName : mappings.keySet()) { final Mapping mapping = mappings.get(cName); mapping.save(new File(indexDirectory, cName.toLowerCase() + ".idx")); } } if (doBuildTables) // don't write until after running classification mappers, as they add tags to reference sequences referencesDB.save(new File(indexDirectory, "ref.idx"), new File(indexDirectory, "ref.db"), new File(indexDirectory, "ref.inf"), saveFirstWordOfReferenceHeaderOnly); if (gffFiles.size() > 0) { // setup gene item creator, in particular accession mapping final GeneItemCreator creator = AAdderBuild.setupCreator(null, class2AccessionFile); // obtains the gene annotations: Map>> dnaId2list = AAdderBuild.computeAnnotations(creator, gffFiles); AAdderBuild.saveIndex(INDEX_CREATOR, creator, indexDirectory.getPath(), dnaId2list, referencesDB.refNames()); } } } malt-0.5.2/src/malt/MaltOptions.java000066400000000000000000000235231400455127600173240ustar00rootroot00000000000000/* * MaltOptions.java Copyright (C) 2020. Daniel H. Huson * * (Some files contain contributions from other authors, who are then mentioned separately.) * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * */ package malt; import jloda.util.BlastMode; import malt.data.IAlphabet; /** * maintains the set of Malt options * Daniel Huson, 8.2014 */ public class MaltOptions { private String commandLine; public enum MatchOutputFormat { SAM, Tab, Text; public static MatchOutputFormat valueOfIgnoreCase(String label) { for (MatchOutputFormat type : values()) if (label.equalsIgnoreCase(type.toString())) return type; return null; } } public enum MemoryMode {load, page, map} // load data into memory, load data in pages on demand, use memory mapping private boolean saveUnalignedToRMA; private int maxAlignmentsPerQuery = 25; private int maxAlignmentsPerReference = 1; private double minBitScore = 50; private double maxExpected = 1; private double minProportionIdentity = 0; private boolean doForward = true; private boolean doReverse = true; private BlastMode mode; private MatchOutputFormat matchOutputFormat = MatchOutputFormat.SAM; private boolean sparseSAM = false; private boolean gzipMatches = true; private boolean gzipOrganisms = true; private boolean gzipAlignedReads = true; private boolean gzipUnalignedReads = true; private boolean useWeightedLCA = false; private float lcaCoveragePercent = 80.0f; private float topPercentLCA = 10; private float minSupportPercentLCA = 0.001f; private int minSupportLCA = 1; private float minPercentIdentityLCA = 0.0f; private boolean usePercentIdentityFilterLCA = false; private int maxSeedsPerReference = 20; private int maxSeedsPerOffsetPerFrame = 100; private int shift = 1; private int numberOfThreads = 8; private IAlphabet queryAlphabet; private boolean useReplicateQueryCaching = false; private boolean pairedReads = false; private String contaminantsFile = ""; private boolean parseHeaders; /** * get seed shift step * * @return shift */ public int getShift() { return shift; } /** * set seed shift step * * @param shift */ public void setShift(int shift) { this.shift = shift; } /** * get number of worker threads * * @return threads */ public int getNumberOfThreads() { return numberOfThreads; } /** * set number of worker threads * * @param numberOfThreads */ public void setNumberOfThreads(int numberOfThreads) { this.numberOfThreads = numberOfThreads; } public int getMaxAlignmentsPerQuery() { return maxAlignmentsPerQuery; } public void setMaxAlignmentsPerQuery(int maxAlignmentsPerQuery) { this.maxAlignmentsPerQuery = maxAlignmentsPerQuery; } public int getMaxAlignmentsPerReference() { return maxAlignmentsPerReference; } public void setMaxAlignmentsPerReference(int maxAlignmentsPerReference) { this.maxAlignmentsPerReference = maxAlignmentsPerReference; } public double getMinBitScore() { return minBitScore; } public void setMinBitScore(double minBitScore) { this.minBitScore = minBitScore; } public double getMaxExpected() { return maxExpected; } public void setMaxExpected(double maxExpected) { this.maxExpected = maxExpected; } public double getMinProportionIdentity() { return minProportionIdentity; } public void setMinProportionIdentity(double minProportionIdentity) { this.minProportionIdentity = minProportionIdentity; } public boolean isDoForward() { return doForward; } public void setDoForward(boolean doForward) { this.doForward = doForward; } public boolean isDoReverse() { return doReverse; } public void setDoReverse(boolean doReverse) { this.doReverse = doReverse; } public int getMaxSeedsPerReference() { return maxSeedsPerReference; } public void setMaxSeedsPerReference(int maxSeedsPerReference) { this.maxSeedsPerReference = maxSeedsPerReference; } public int getMaxSeedsPerOffsetPerFrame() { return maxSeedsPerOffsetPerFrame; } public void setMaxSeedsPerOffsetPerFrame(int maxSeedsPerOffsetPerFrame) { this.maxSeedsPerOffsetPerFrame = maxSeedsPerOffsetPerFrame; } public void setSaveUnalignedToRMA(boolean saveUnalignedToRMA) { this.saveUnalignedToRMA = saveUnalignedToRMA; } public boolean isSaveUnalignedToRMA() { return saveUnalignedToRMA; } public BlastMode getMode() { return mode; } public void setMode(BlastMode mode) { this.mode = mode; } public MatchOutputFormat getMatchOutputFormat() { return matchOutputFormat; } public void setMatchOutputFormat(MatchOutputFormat matchOutputFormat) { this.matchOutputFormat = matchOutputFormat; } public void setMatchOutputFormat(String matchOutputFormat) { this.matchOutputFormat = MatchOutputFormat.valueOfIgnoreCase(matchOutputFormat); } public boolean isGzipMatches() { return gzipMatches; } public void setGzipMatches(boolean gzipMatches) { this.gzipMatches = gzipMatches; } public boolean isGzipOrganisms() { return gzipOrganisms; } public void setGzipOrganisms(boolean gzipOrganisms) { this.gzipOrganisms = gzipOrganisms; } public boolean isGzipAlignedReads() { return gzipAlignedReads; } public void setGzipAlignedReads(boolean gzipAlignedReads) { this.gzipAlignedReads = gzipAlignedReads; } public boolean isGzipUnalignedReads() { return gzipUnalignedReads; } public void setGzipUnalignedReads(boolean gzipUnalignedReads) { this.gzipUnalignedReads = gzipUnalignedReads; } public float getTopPercentLCA() { return topPercentLCA; } public void setTopPercentLCA(float topPercentLCA) { this.topPercentLCA = topPercentLCA; } public int getMinSupportLCA() { return minSupportLCA; } public void setMinSupportLCA(int minSupportLCA) { this.minSupportLCA = minSupportLCA; } public float getMinSupportPercentLCA() { return minSupportPercentLCA; } public void setMinSupportPercentLCA(float minSupportPercentLCA) { this.minSupportPercentLCA = minSupportPercentLCA; } public float getMinPercentIdentityLCA() { return minPercentIdentityLCA; } public void setMinPercentIdentityLCA(float minPercentIdentityLCA) { this.minPercentIdentityLCA = minPercentIdentityLCA; } public IAlphabet getQueryAlphabet() { return queryAlphabet; } public void setQueryAlphabet(IAlphabet queryAlphabet) { this.queryAlphabet = queryAlphabet; } public boolean isUseReplicateQueryCaching() { return useReplicateQueryCaching; } public void setUseReplicateQueryCaching(boolean useReplicateQueryCaching) { this.useReplicateQueryCaching = useReplicateQueryCaching; } public boolean isUseWeightedLCA() { return useWeightedLCA; } public void setUseWeightedLCA(boolean useWeightedLCA) { this.useWeightedLCA = useWeightedLCA; } public float getLcaCoveragePercent() { return lcaCoveragePercent; } public void setLcaCoveragePercent(float lcaCoveragePercent) { this.lcaCoveragePercent = lcaCoveragePercent; } public boolean isPairedReads() { return pairedReads; } public void setPairedReads(boolean pairedReads) { this.pairedReads = pairedReads; } public boolean isUsePercentIdentityFilterLCA() { return usePercentIdentityFilterLCA; } public void setUsePercentIdentityFilterLCA(boolean usePercentIdentityFilterLCA) { this.usePercentIdentityFilterLCA = usePercentIdentityFilterLCA; } /** * get the appropriate suffix for a matches output file * * @return suffix */ public String getMatchesOutputSuffix() { if (matchOutputFormat == MatchOutputFormat.SAM) return "." + mode.name().toLowerCase() + ".sam"; else if (matchOutputFormat == MatchOutputFormat.Tab) return "." + mode.name().toLowerCase() + ".tab"; else return "." + mode.name().toLowerCase(); } public boolean isSparseSAM() { return sparseSAM; } public void setSparseSAM(boolean sparseSAM) { this.sparseSAM = sparseSAM; } public void setCommandLine(String commandLine) { this.commandLine = commandLine; } public String getCommandLine() { return commandLine; } public String getContaminantsFile() { return contaminantsFile; } public void setContaminantsFile(String contaminantsFile) { this.contaminantsFile = contaminantsFile; } public boolean isParseHeaders() { return parseHeaders; } public void setParseHeaders(boolean parseHeaders) { this.parseHeaders = parseHeaders; } } malt-0.5.2/src/malt/MaltRun.java000066400000000000000000001006751400455127600164410ustar00rootroot00000000000000/* * MaltRun.java Copyright (C) 2020. Daniel H. Huson * * (Some files contain contributions from other authors, who are then mentioned separately.) * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * */ package malt; import jloda.swing.util.ArgsOptions; import jloda.swing.util.ResourceManager; import jloda.util.*; import malt.align.AlignerOptions; import malt.align.BlastStatisticsHelper; import malt.align.DNAScoringMatrix; import malt.align.ProteinScoringMatrix; import malt.data.*; import malt.io.*; import malt.mapping.MappingManager; import malt.util.Utilities; import megan.classification.ClassificationManager; import megan.core.Document; import megan.genes.GeneItemAccessor; import megan.main.Megan6; import megan.util.ReadMagnitudeParser; import java.io.BufferedWriter; import java.io.File; import java.io.IOException; import java.io.OutputStreamWriter; import java.security.NoSuchAlgorithmException; import java.security.NoSuchProviderException; import java.security.spec.InvalidKeySpecException; import java.util.BitSet; import java.util.LinkedList; import java.util.List; import java.util.concurrent.CountDownLatch; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; /** * the MALT metagenome alignment tool * Daniel Huson, 8.2014 */ public class MaltRun { public static String version; private long totalReads = 0; private long totalAlignedReads = 0; private long totalAlignments = 0; /** * launch the MALT program */ public static void main(String[] args) { ResourceManager.addResourceRoot(Megan6.class, "megan.resources"); try { PeakMemoryUsageMonitor.start(); MaltRun program = new MaltRun(); ResourceManager.setWarningMissingIcon(false); ProgramProperties.setProgramIcons(ResourceManager.getIcons("malt-run16.png", "malt-run32.png", "malt-run48.png")); ProgramProperties.setProgramName("MaltRun"); ProgramProperties.setProgramVersion(Version.SHORT_DESCRIPTION); program.run(args); System.err.println("Total time: " + PeakMemoryUsageMonitor.getSecondsSinceStartString()); System.err.println("Peak memory: " + PeakMemoryUsageMonitor.getPeakUsageString()); if (!ArgsOptions.hasMessageWindow()) System.exit(0); else System.err.println("DONE - close window to quit"); } catch (Exception ex) { if (ex.getMessage() == null || !ex.getMessage().startsWith("Help")) Basic.caught(ex); if (!ArgsOptions.hasMessageWindow()) System.exit(1); else System.err.println("DONE - close window to quit"); } } /** * run the program */ public void run(final String[] args) throws UsageException, IOException, InvalidKeySpecException, NoSuchAlgorithmException, NoSuchProviderException { version = Basic.getVersion(this.getClass()); final MaltOptions maltOptions = new MaltOptions(); final AlignerOptions alignerOptions = new AlignerOptions(); // parse commandline options: final ArgsOptions options = new ArgsOptions(args, this, "Aligns sequences using MALT (MEGAN alignment tool)"); options.setAuthors("Daniel H. Huson"); options.setVersion(ProgramProperties.getProgramVersion()); options.setLicense("Copyright (C) 2020 Daniel H. Huson. This program comes with ABSOLUTELY NO WARRANTY."); options.comment("Mode:"); maltOptions.setMode(BlastMode.valueOfIgnoreCase(options.getOptionMandatory("m", "mode", "Program mode", BlastMode.values(), BlastMode.BlastX.toString()))); alignerOptions.setAlignmentType(options.getOption("at", "alignmentType", "Type of alignment to be performed", AlignerOptions.AlignmentMode.values(), alignerOptions.getAlignmentType().toString())); SequenceType querySequenceType = Utilities.getQuerySequenceTypeFromMode(maltOptions.getMode()); SequenceType referenceSequenceType = Utilities.getReferenceSequenceTypeFromMode(maltOptions.getMode()); options.comment("Input:"); List inputFileNames = options.getOptionMandatory("i", "inFile", "Input file(s) containing queries in FastA or FastQ format (gzip or zip ok)", new LinkedList<>()); String indexDirectory = options.getOptionMandatory("d", "index", "Index directory as generated by MaltBuild", ""); options.comment("Output:"); final List outputRMAFileNames = options.getOption("o", "output", "Output RMA file(s) or directory or stdout", new LinkedList<>()); if (outputRMAFileNames.size() > 0 || options.isDoHelp()) maltOptions.setSaveUnalignedToRMA(options.getOption("iu", "includeUnaligned", "Include unaligned queries in RMA output file", false)); final List outputMatchesFileNames = options.getOption("a", "alignments", "Output alignment file(s) or directory or stdout", new LinkedList<>()); if (outputMatchesFileNames.size() > 0 || options.isDoHelp()) { maltOptions.setMatchOutputFormat(options.getOption("f", "format", "Alignment output format", MaltOptions.MatchOutputFormat.values(), maltOptions.getMatchOutputFormat().toString())); maltOptions.setGzipMatches(options.getOption("za", "gzipAlignments", "Compress alignments using gzip", maltOptions.isGzipMatches())); } if ((maltOptions.getMatchOutputFormat() == MaltOptions.MatchOutputFormat.SAM && maltOptions.getMode() == BlastMode.BlastN) || options.isDoHelp()) { alignerOptions.setSamSoftClipping(options.getOption("ssc", "samSoftClip", "Use soft clipping in SAM files (BlastN mode only)", alignerOptions.isSamSoftClipping())); } if (maltOptions.getMatchOutputFormat() == MaltOptions.MatchOutputFormat.SAM || options.isDoHelp()) { maltOptions.setSparseSAM(options.getOption("sps", "sparseSAM", "Produce sparse SAM format (smaller, faster, but only suitable for MEGAN)", maltOptions.isSparseSAM())); } final List outputAlignedFileNames = options.getOption("oa", "outAligned", "Aligned reads output file(s) or directory or stdout", new LinkedList<>()); if (outputAlignedFileNames.size() > 0 || options.isDoHelp()) { maltOptions.setGzipAlignedReads(options.getOption("zal", "gzipAligned", "Compress aligned reads output using gzip", maltOptions.isGzipAlignedReads())); } final List outputUnAlignedFileNames = options.getOption("ou", "outUnaligned", "Unaligned reads output file(s) or directory or stdout", new LinkedList<>()); if (outputUnAlignedFileNames.size() > 0 || options.isDoHelp()) { maltOptions.setGzipUnalignedReads(options.getOption("zul", "gzipUnaligned", "Compress unaligned reads output using gzip", maltOptions.isGzipUnalignedReads())); } options.comment("Performance:"); maltOptions.setNumberOfThreads(options.getOption("t", "numThreads", "Number of worker threads", Runtime.getRuntime().availableProcessors())); final MaltOptions.MemoryMode memoryMode = MaltOptions.MemoryMode.valueOf(options.getOption("mem", "memoryMode", "Memory mode", MaltOptions.MemoryMode.values(), MaltOptions.MemoryMode.load.toString())); final int maxNumberOfSeedShapes = options.getOption("mt", "maxTables", "Set the maximum number of seed tables to use (0=all)", 0); maltOptions.setUseReplicateQueryCaching(options.getOption("rqc", "replicateQueryCache", "Cache results for replicated queries", false)); options.comment("Filter:"); maltOptions.setMinBitScore(options.getOption("b", "minBitScore", "Minimum bit score", maltOptions.getMinBitScore())); maltOptions.setMaxExpected(options.getOption("e", "maxExpected", "Maximum expected score", maltOptions.getMaxExpected())); maltOptions.setMinProportionIdentity(options.getOption("id", "minPercentIdentity", "Minimum percent identity", 100 * maltOptions.getMinProportionIdentity()) / 100.0); maltOptions.setMaxAlignmentsPerQuery(options.getOption("mq", "maxAlignmentsPerQuery", "Maximum number of alignments per query", maltOptions.getMaxAlignmentsPerQuery())); maltOptions.setMaxAlignmentsPerReference(options.getOption("mrf", "maxAlignmentsPerRef", "Maximum number of (non-overlapping) alignments per reference", maltOptions.getMaxAlignmentsPerReference())); if ((maltOptions.getMode() == BlastMode.BlastN || options.isDoHelp())) { options.comment("BlastN parameters:"); alignerOptions.setMatchScore(options.getOption("ma", "matchScore", "Match score", alignerOptions.getMatchScore())); alignerOptions.setMismatchScore(options.getOption("mm", "mismatchScore", "Mismatch score", alignerOptions.getMismatchScore())); alignerOptions.setLambda(options.getOption("la", "setLambda", "Parameter Lambda for BLASTN statistics", alignerOptions.getLambda())); alignerOptions.setK(options.getOption("K", "setK", "Parameter K for BLASTN statistics", (float) alignerOptions.getK())); } String nameOfProteinScoringMatrix = null; if (maltOptions.getMode() == BlastMode.BlastP || maltOptions.getMode() == BlastMode.BlastX || options.isDoHelp()) { options.comment("BlastP and BlastX parameters:"); nameOfProteinScoringMatrix = options.getOption("psm", "subMatrix", "Protein substitution matrix to use", ProteinScoringMatrix.ScoringScheme.values(), ProteinScoringMatrix.ScoringScheme.BLOSUM62.toString()); } if (querySequenceType == SequenceType.DNA || options.isDoHelp()) { options.comment("DNA query parameters:"); maltOptions.setDoReverse(!options.getOption("fo", "forwardOnly", "Align query forward strand only", false)); maltOptions.setDoForward(!options.getOption("ro", "reverseOnly", "Align query reverse strand only", false)); } options.comment("LCA parameters:"); final String[] cNames = (options.isDoHelp() ? ClassificationManager.getAllSupportedClassifications().toArray(new String[0]) : MappingManager.determineAvailableMappings(indexDirectory)); maltOptions.setTopPercentLCA(options.getOption("top", "topPercent", "Top percent value for LCA algorithm", maltOptions.getTopPercentLCA())); maltOptions.setMinSupportPercentLCA(options.getOption("supp", "minSupportPercent", "Min support value for LCA algorithm as a percent of assigned reads (0==off)", maltOptions.getMinSupportPercentLCA())); maltOptions.setMinSupportLCA(options.getOption("sup", "minSupport", "Min support value for LCA algorithm (overrides --minSupportPercent)", 0)); if (maltOptions.getMinSupportLCA() == 0) { maltOptions.setMinSupportLCA(1); } else if (maltOptions.getMinSupportLCA() > 0) { maltOptions.setMinSupportPercentLCA(0); // if user sets minSupport,then turn of minSupportPercentLCA if (options.isVerbose()) System.err.println("\t(--minSupportPercent: overridden, set to 0)"); } maltOptions.setMinPercentIdentityLCA(options.getOption("mpi", "minPercentIdentityLCA", "Min percent identity used by LCA algorithm", maltOptions.getMinPercentIdentityLCA())); maltOptions.setUsePercentIdentityFilterLCA(options.getOption("mif", "useMinPercentIdentityFilterLCA", "Use percent identity assignment filter", maltOptions.isUsePercentIdentityFilterLCA())); maltOptions.setUseWeightedLCA(options.getOption("-wlca", "weightedLCA", "Use the weighted LCA for taxonomic assignment", false)); if (options.isDoHelp() || maltOptions.isUseWeightedLCA()) maltOptions.setLcaCoveragePercent(options.getOption("-lcp", "lcaCoveragePercent", "Set the percent for the LCA to cover", Document.DEFAULT_LCA_COVERAGE_PERCENT_SHORT_READS)); ReadMagnitudeParser.setEnabled(options.getOption("mag", "magnitudes", "Reads have magnitudes (to be used in taxonomic or functional analysis)", false)); maltOptions.setContaminantsFile(options.getOption("-cf", "conFile", "File of contaminant taxa (one Id or name per line)", "")); options.comment("Heuristics:"); maltOptions.setMaxSeedsPerOffsetPerFrame(options.getOption("spf", "maxSeedsPerFrame", "Maximum number of seed matches per offset per read frame", maltOptions.getMaxSeedsPerOffsetPerFrame())); maltOptions.setMaxSeedsPerReference(options.getOption("spr", "maxSeedsPerRef", "Maximum number of seed matches per read and reference", maltOptions.getMaxSeedsPerReference())); maltOptions.setShift(options.getOption("sh", "seedShift", "Seed shift", maltOptions.getShift())); options.comment("Banded alignment parameters:"); alignerOptions.setGapOpenPenalty(options.getOption("go", "gapOpen", "Gap open penalty", referenceSequenceType == SequenceType.DNA ? 7 : 11)); alignerOptions.setGapExtensionPenalty(options.getOption("ge", "gapExtend", "Gap extension penalty", referenceSequenceType == SequenceType.DNA ? 3 : 1)); alignerOptions.setBand(options.getOption("bd", "band", "Band width/2 for banded alignment", alignerOptions.getBand())); options.comment(ArgsOptions.OTHER); int replicateQueryCacheBits = options.getOption("rqcb", "replicateQueryCacheBits", "Bits used for caching replicate queries (size is then 2^bits)", 20); final boolean showAPart = options.getOption("xP", "xPart", "Show part of the table in human readable form for debugging", false); options.done(); Basic.setDebugMode(options.isVerbose()); maltOptions.setCommandLine(Basic.toString(args, " ")); // END OF OPTIONS if (replicateQueryCacheBits < 10 || replicateQueryCacheBits > 31) throw new IOException("replicateQueryCacheBits: supported range is 10-31"); // make sure that the index contains the correct type of sequences: { SequenceType indexSequencesType = ReferencesHashTableAccess.getIndexSequenceType(indexDirectory); if (referenceSequenceType != indexSequencesType) throw new IOException("--mode " + maltOptions.getMode() + " not compatible with index containing sequences of type: " + indexSequencesType); } if (querySequenceType == SequenceType.Protein) { maltOptions.setQueryAlphabet(ProteinAlphabet.getInstance()); } else if (querySequenceType == SequenceType.DNA) { maltOptions.setQueryAlphabet(DNA5.getInstance()); } else throw new UsageException("Undefined query sequence type: " + querySequenceType); if (referenceSequenceType == SequenceType.Protein) { alignerOptions.setScoringMatrix(ProteinScoringMatrix.create(nameOfProteinScoringMatrix)); alignerOptions.setReferenceIsDNA(false); alignerOptions.setLambdaAndK(BlastStatisticsHelper.lookupLambdaAndK(nameOfProteinScoringMatrix, alignerOptions.getGapOpenPenalty(), alignerOptions.getGapExtensionPenalty())); } else if (referenceSequenceType == SequenceType.DNA) { alignerOptions.setScoringMatrix(new DNAScoringMatrix(alignerOptions.getMatchScore(), alignerOptions.getMismatchScore())); alignerOptions.setReferenceIsDNA(true); } else throw new UsageException("Undefined reference sequence type: " + referenceSequenceType); // check consistency of all options: if (inputFileNames.size() == 0) throw new UsageException("You must specify at least one input file"); Utilities.checkFileExists(new File(inputFileNames.iterator().next())); for (String aName : outputRMAFileNames) { if (outputAlignedFileNames.contains(aName)) throw new UsageException("-a and -o options: Illegal for both to contain the same file name: " + aName); } for (String aName : outputAlignedFileNames) { if (outputRMAFileNames.contains(aName)) throw new UsageException("-a and -o options: Illegal for both to contain the same file name: " + aName); } if (!maltOptions.isDoForward() && !maltOptions.isDoReverse()) throw new UsageException("Illegal to specify both --forwardOnly and --reverseOnly"); Utilities.checkFileExists(new File(indexDirectory)); try { ReferencesHashTableAccess.checkFilesExist(indexDirectory, 0); } catch (IOException ex) { throw new IOException("Index '" + indexDirectory + "' appears to be incomplete: " + ex); } { int fileNumber = 0; for (String inFile : inputFileNames) { if (Basic.fileExistsAndIsNonEmpty(inFile)) { final String rmaOutputFile = getOutputFileName(fileNumber, inputFileNames, outputRMAFileNames, ".rma6", false); if (Basic.notBlank(rmaOutputFile)) Basic.checkFileWritable(rmaOutputFile, true); final String matchesOutputFile = getOutputFileName(fileNumber, inputFileNames, outputMatchesFileNames, maltOptions.getMatchesOutputSuffix(), maltOptions.isGzipMatches()); if (Basic.notBlank(matchesOutputFile)) Basic.checkFileWritable(matchesOutputFile, true); final String alignedReadsOutputFile = getOutputFileName(fileNumber, inputFileNames, outputAlignedFileNames, "-aligned.fna", maltOptions.isGzipAlignedReads()); if (Basic.notBlank(alignedReadsOutputFile)) Basic.checkFileWritable(alignedReadsOutputFile, true); final String unalignedReadsOutputFile = getOutputFileName(fileNumber, inputFileNames, outputUnAlignedFileNames, "-unaligned.fna", maltOptions.isGzipUnalignedReads()); if (Basic.notBlank(unalignedReadsOutputFile)) Basic.checkFileWritable(unalignedReadsOutputFile, true); } } } // load the index: System.err.println("--- LOADING ---:"); // load the reference file: final ReferencesDBAccess referencesDB = new ReferencesDBAccess(memoryMode, new File(indexDirectory, "ref.idx"), new File(indexDirectory, "ref.db"), new File(indexDirectory, "ref.inf")); alignerOptions.setReferenceDatabaseLength(referencesDB.getNumberOfLetters()); int numberOfTables = ReferencesHashTableAccess.determineNumberOfTables(indexDirectory); if (maxNumberOfSeedShapes > 0 && maxNumberOfSeedShapes < numberOfTables) { System.err.println("Using " + maxNumberOfSeedShapes + " of " + numberOfTables + " available seed shapes"); numberOfTables = maxNumberOfSeedShapes; } // load all tables: final ReferencesHashTableAccess[] hashTables = new ReferencesHashTableAccess[numberOfTables]; for (int t = 0; t < numberOfTables; t++) { System.err.println("LOADING table (" + t + ") ..."); hashTables[t] = new ReferencesHashTableAccess(memoryMode, indexDirectory, t); System.err.printf("Table size:%,15d%n", hashTables[t].size()); if (showAPart) hashTables[t].showAPart(); } // table.show(); // load mapping files, if we are going to generate RMA if (outputRMAFileNames.size() > 0) { MappingManager.loadMappings(cNames, indexDirectory); } final GeneItemAccessor geneTableAccess; if ((new File(indexDirectory, "aadd.idx")).exists()) { geneTableAccess = new GeneItemAccessor(new File(indexDirectory, "aadd.idx"), new File(indexDirectory, "aadd.dbx")); maltOptions.setParseHeaders(true); } else geneTableAccess = null; // run alignment for each input file: System.err.println("--- ALIGNING ---:"); if (maltOptions.isUseReplicateQueryCaching()) AlignmentEngine.activateReplicateQueryCaching(replicateQueryCacheBits); { int fileNumber = 0; for (String inFile : inputFileNames) { try { if (Basic.fileExistsAndIsNonEmpty(inFile)) { final String rmaOutputFile = getOutputFileName(fileNumber, inputFileNames, outputRMAFileNames, ".rma6", false); final String matchesOutputFile = getOutputFileName(fileNumber, inputFileNames, outputMatchesFileNames, maltOptions.getMatchesOutputSuffix(), maltOptions.isGzipMatches()); final String alignedReadsOutputFile = getOutputFileName(fileNumber, inputFileNames, outputAlignedFileNames, "-aligned.fna", maltOptions.isGzipAlignedReads()); final String unalignedReadsOutputFile = getOutputFileName(fileNumber, inputFileNames, outputUnAlignedFileNames, "-unaligned.fna", maltOptions.isGzipUnalignedReads()); launchAlignmentThreads(alignerOptions, maltOptions, inFile, rmaOutputFile, matchesOutputFile, alignedReadsOutputFile, unalignedReadsOutputFile, referencesDB, hashTables, geneTableAccess); } else { System.err.println("File not found: '" + inFile + "', skipped"); } } catch (IOException ex) { System.err.println("Exception for file: '" + inFile + "', skipped (" + ex + ")"); } finally { fileNumber++; } } } // close everything: referencesDB.close(); for (int t = 0; t < numberOfTables; t++) { hashTables[t].close(); } AlignmentEngine.reportStats(); if (inputFileNames.size() > 1) { System.err.printf("Number of input files: %10d%n", inputFileNames.size()); System.err.printf("Total num. of queries: %10d%n", totalReads); System.err.printf("Total aligned queries: %10d%n", totalAlignedReads); System.err.printf("Total num. alignments: %10d%n", totalAlignments); } } /** * run search on file of input sequences */ private void launchAlignmentThreads(final AlignerOptions alignerOptions, final MaltOptions maltOptions, final String infile, final String rmaOutputFile, final String matchesOutputFile, final String alignedReadsOutputFile, final String unalignedReadsOutputFile, final ReferencesDBAccess referencesDB, final ReferencesHashTableAccess[] tables, final GeneItemAccessor geneTableAccess) throws IOException { final FastAReader fastAReader = new FastAReader(infile, maltOptions.getQueryAlphabet(), new ProgressPercentage("+++++ Aligning file: " + infile)); final String matchesOutputFileUsed; final boolean usingTemporarySAMOutputFile; if (matchesOutputFile != null && maltOptions.getMatchOutputFormat() == MaltOptions.MatchOutputFormat.SAM && !maltOptions.isSparseSAM()) { matchesOutputFileUsed = Basic.getTemporaryFileName(matchesOutputFile); usingTemporarySAMOutputFile = true; } else { matchesOutputFileUsed = matchesOutputFile; usingTemporarySAMOutputFile = false; } final FileWriterRanked matchesWriter = (matchesOutputFileUsed != null ? new FileWriterRanked(matchesOutputFileUsed, maltOptions.getNumberOfThreads(), 1) : null); final RMA6Writer rmaWriter = (rmaOutputFile != null ? new RMA6Writer(maltOptions, rmaOutputFile) : null); final FileWriterRanked alignedReadsWriter = (alignedReadsOutputFile != null ? new FileWriterRanked(alignedReadsOutputFile, maltOptions.getNumberOfThreads(), 1) : null); final FileWriterRanked unalignedReadsWriter = (unalignedReadsOutputFile != null ? new FileWriterRanked(unalignedReadsOutputFile, maltOptions.getNumberOfThreads(), 1) : null); if (matchesWriter == null && rmaWriter == null && alignedReadsWriter == null && unalignedReadsWriter == null) System.err.println("Warning: no output specified"); if (matchesWriter != null) { if (maltOptions.getMatchOutputFormat() == MaltOptions.MatchOutputFormat.Text) matchesWriter.writeFirst(BlastTextHelper.getBlastTextHeader(maltOptions.getMode())); else if (maltOptions.getMatchOutputFormat() == MaltOptions.MatchOutputFormat.SAM && !usingTemporarySAMOutputFile) { matchesWriter.writeFirst(SAMHelper.getSAMHeader(maltOptions.getMode(), maltOptions.getCommandLine())); } } final AlignmentEngine[] alignmentEngines = new AlignmentEngine[maltOptions.getNumberOfThreads()]; final ExecutorService executor = Executors.newFixedThreadPool(maltOptions.getNumberOfThreads()); final CountDownLatch countDownLatch = new CountDownLatch(maltOptions.getNumberOfThreads()); try { // launch the worker threads for (int thread = 0; thread < maltOptions.getNumberOfThreads(); thread++) { final int threadNumber = thread; executor.execute(() -> { try { alignmentEngines[threadNumber] = new AlignmentEngine(threadNumber, maltOptions, alignerOptions, referencesDB, tables, fastAReader, matchesWriter, rmaWriter, alignedReadsWriter, unalignedReadsWriter, geneTableAccess); alignmentEngines[threadNumber].runOuterLoop(); alignmentEngines[threadNumber].finish(); } catch (Exception ex) { Basic.caught(ex); System.exit(1); // just die... } finally { countDownLatch.countDown(); } }); } try { countDownLatch.await(); // await completion of alignment threads } catch (InterruptedException e) { Basic.caught(e); } finally { fastAReader.close(); } } finally { // shut down threads: executor.shutdownNow(); } if (matchesWriter != null) { if (maltOptions.getMatchOutputFormat() == MaltOptions.MatchOutputFormat.Text) matchesWriter.writeLast(BlastTextHelper.FILE_FOOTER_BLAST); matchesWriter.close(); System.err.println("Alignments written to file: " + matchesOutputFileUsed); } if (rmaWriter != null) { rmaWriter.close(maltOptions.getContaminantsFile()); System.err.println("Analysis written to file: " + rmaOutputFile); } // if using temporary file, prepend @SQ lines, if requested, and sort by query name, if requested if (usingTemporarySAMOutputFile) { final BufferedWriter w = new BufferedWriter(new OutputStreamWriter(Basic.getOutputStreamPossiblyZIPorGZIP(matchesOutputFile))); w.write(SAMHelper.getSAMHeader(maltOptions.getMode(), maltOptions.getCommandLine())); // prepend SQ lines { final BitSet allIds = new BitSet(); for (AlignmentEngine engine : alignmentEngines) { allIds.or(engine.getAlignedReferenceIds()); } if (allIds.cardinality() > 0) { ProgressPercentage progress = new ProgressPercentage("Prepending @SQ lines to SAM file: " + matchesOutputFile, allIds.size()); for (int r = allIds.nextSetBit(0); r != -1; r = allIds.nextSetBit(r + 1)) { w.write("@SQ\tSN:" + (Basic.toString(Utilities.getFirstWordSkipLeadingGreaterSign(referencesDB.getHeader(r)))) + "\tLN:" + referencesDB.getSequenceLength(r)); w.write('\n'); progress.incrementProgress(); } progress.close(); } } // copy matches { final FileLineIterator it = new FileLineIterator(matchesOutputFileUsed); final ProgressPercentage progress = new ProgressPercentage("Copying from temporary file:", it.getMaximumProgress()); while (it.hasNext()) { w.write(it.next()); w.write("\tRG:Z:1\n"); progress.incrementProgress(); } it.close(); progress.close(); } w.close(); if (new File(matchesOutputFileUsed).delete()) System.err.println("Deleted temporary file: " + matchesOutputFileUsed); } if (alignedReadsWriter != null) { // merge all thread-specific taxon profiles. This can be quite major computation... alignedReadsWriter.close(); System.err.println("Aligned reads written to file: " + alignedReadsOutputFile); } if (unalignedReadsWriter != null) { // merge all thread-specific taxon profiles. This can be quite major computation... unalignedReadsWriter.close(); System.err.println("Unaligned reads written to file: " + unalignedReadsOutputFile); } final long countReads = AlignmentEngine.getTotalSequencesProcessed(alignmentEngines); totalReads += countReads; final long countAlignedReads = AlignmentEngine.getTotalSequencesWithAlignments(alignmentEngines); totalAlignedReads += countAlignedReads; final long countAlignments = AlignmentEngine.getTotalAlignments(alignmentEngines); totalAlignments += countAlignments; System.err.printf("Num. of queries: %10d%n", countReads); System.err.printf("Aligned queries: %10d%n", countAlignedReads); System.err.printf("Num. alignments: %10d%n", countAlignments); } /** * creates the output file name */ private String getOutputFileName(final int fileNumber, final List inFiles, final List outFiles, final String suffix, final boolean gzip) throws IOException { final String fileName; if (outFiles.size() == 0) fileName = null; else if (outFiles.size() == 1) { if (outFiles.get(0).equalsIgnoreCase("stdout")) { fileName = "stdout"; } else if (inFiles.size() == 1 && !Basic.isDirectory(outFiles.get(0))) { String outfileName = outFiles.get(0); if (gzip && !outfileName.endsWith(".gz")) fileName = outfileName + ".gz"; else fileName = outfileName; } else { if (!Basic.isDirectory(outFiles.get(0))) throw new IOException("Specified output location does not exist or is not a directory: " + outFiles.get(0)); final File infile = new File(inFiles.get(fileNumber)); String outfileName = Basic.getFileNameWithoutPath(inFiles.get(fileNumber)); if (Basic.isZIPorGZIPFile(outfileName)) outfileName = Basic.replaceFileSuffix(outfileName, ""); outfileName = Basic.replaceFileSuffix(outfileName, suffix); final File outfile = new File(outFiles.get(0), outfileName); if (infile.equals(outfile)) throw new IOException("Output file equals input file: " + infile); if (gzip && !outfile.toString().endsWith(".gz")) fileName = outfile.toString() + ".gz"; else fileName = outfile.toString(); } } else { if (inFiles.size() != outFiles.size()) throw new IOException("Number of output files=" + outFiles.size() + " must equal 1 or number of input files (" + inFiles.size() + ")"); if (gzip && !outFiles.get(fileNumber).endsWith(".gz")) fileName = outFiles.get(fileNumber) + ".gz"; else fileName = outFiles.get(fileNumber); } if (fileName != null && !fileName.equalsIgnoreCase("stdout")) { Basic.checkFileWritable(fileName, true); } return fileName; } } malt-0.5.2/src/malt/Notes000066400000000000000000000010561400455127600152200ustar00rootroot00000000000000Difference between sass-n and malt: Weird gap spacing: In the following alignment C-T-C seems weird, but this is correct, as this scores better than C--T: Score = 25.6 bits (27), Expected = 1e-05 Identities = 22/25 (88%), Gaps = 2/25 (8%) Strand = Plus / Plus Query: 1 CCCACAACCC-T-CCACAAGGGGGG 23 |||||||||| | |||||| ||||| Sbjct: 1 CCCACAACCCATCCCACAATGGGGG 25 Be very careful with classes that have static components that are initialized upon use. These can cause problems when used in a multi-threaded context malt-0.5.2/src/malt/TestIO.java000066400000000000000000000263621400455127600162260ustar00rootroot00000000000000/* * TestIO.java Copyright (C) 2020. Daniel H. Huson * * (Some files contain contributions from other authors, who are then mentioned separately.) * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * */ package malt; import jloda.util.Basic; import java.io.*; import java.nio.ByteBuffer; import java.nio.channels.FileChannel; import java.util.Random; /** * Test IO * Daniel Huson, 8.2014 */ public class TestIO { public static final byte[] MAGIC_NUMBER = "HEAT-IDX".getBytes(); public static void main(String[] args) throws IOException { String choice = (args.length == 0 ? "wfrf wnrn" : Basic.toString(args, " ")); // create a test buffer int[][] arrays = createArrays(); long start = System.currentTimeMillis(); if (choice.contains("wn")) { // do the first test (the normal way of writing files) normalToFile("/Users/huson/tmp/heat/first.idx", arrays); } long one = System.currentTimeMillis(); if (choice.contains("wn")) System.out.println("normal write: " + (one - start)); if (choice.contains("wf")) { // use the faster nio stuff fasterToFile("/Users/huson/tmp/heat/second.idx", arrays); } long two = System.currentTimeMillis(); // print the result if (choice.contains("wf")) System.out.println("faster write: " + (two - one)); System.out.println(); long a = System.currentTimeMillis(); int[][] normalResults = null; if (choice.contains("rn")) { normalResults = normalFromFile("/Users/huson/tmp/heat/first.idx"); } long b = System.currentTimeMillis(); if (choice.contains("rn")) System.out.println("normal read: " + (b - a)); if (normalResults != null) { if (arrays.length != normalResults.length) throw new IOException("arrays.length=" + arrays.length + "!= normalResults.length: " + normalResults.length); for (int i = 0; i < arrays.length; i++) { if (arrays[i].length != normalResults[i].length) { throw new IOException("array[" + i + "].length=" + arrays[i].length + "!= normalResults[" + i + "].length: " + normalResults[i].length); } } System.err.println("normalResults ok"); } int[][] fasterResults = null; if (choice.contains("rf")) { fasterResults = fasterFromFile("/Users/huson/tmp/heat/second.idx"); } long c = System.currentTimeMillis(); if (choice.contains("rf")) System.out.println("faster read: " + (c - b)); if (fasterResults != null) { if (arrays.length != fasterResults.length) throw new IOException("arrays.length=" + arrays.length + "!= fasterResults.length: " + fasterResults.length); for (int i = 0; i < arrays.length; i++) { if (arrays[i].length != fasterResults[i].length) { throw new IOException("array[" + i + "].length=" + arrays[i].length + "!= fasterResults[" + i + "].length: " + fasterResults[i].length); } } System.err.println("fasterResults ok"); } } public static void fasterToFile(String fileName, int[][] arrays) throws IOException { // final long maxNumberOfInts = Integer.MAX_VALUE / 4 - MAGIC_NUMBER.length; final long maxNumberOfInts = 100 * arrays.length; // about 10 files int start = 0; int fileCount = 0; while (start < arrays.length) { int numberOfInts = (fileCount == 0 ? 2 : 1); int end = start; while (end < arrays.length && numberOfInts + arrays[end].length < maxNumberOfInts) { numberOfInts += arrays[end++].length; } final File file = new File(replaceFileSuffix(fileName, "-" + fileCount + ".idx")); if (file.exists() && !file.delete()) throw new IOException("Failed to delete existing file: " + file); final RandomAccessFile out = new RandomAccessFile(file, "rw"); final FileChannel fc = out.getChannel(); final int size = 4 * numberOfInts + MAGIC_NUMBER.length; final ByteBuffer buf = fc.map(FileChannel.MapMode.READ_WRITE, 0, size); buf.put(MAGIC_NUMBER); // magic number comes first buf.putInt(fileCount); // file number comes second if (fileCount == 0) buf.putInt(arrays.length); // first file additionally contains total number of arrays for (int i = start; i < end; i++) { final int[] array = arrays[i]; final int length = array.length; buf.putInt(length); for (int j = 1; j < length; j++) buf.putInt(array[j]); } out.close(); start = end; fileCount++; } } public static int[][] fasterFromFile(String fileName) throws IOException { int[][] arrays = null; int theArraysLength = Integer.MAX_VALUE; int fileCount = 0; int arrayNumber = 0; // which array are we reading while (arrayNumber < theArraysLength) { final File file = new File(replaceFileSuffix(fileName, "-" + fileCount + ".idx")); final FileInputStream ins = new FileInputStream(file); final FileChannel fc = ins.getChannel(); final ByteBuffer buf = fc.map(FileChannel.MapMode.READ_ONLY, 0, fc.size()); byte[] theMagicNumber = new byte[MAGIC_NUMBER.length]; buf.get(theMagicNumber, 0, theMagicNumber.length); // magic number comes first int theFileCount = buf.getInt(); if (theFileCount != fileCount) throw new IOException("Incorrect file count=" + theFileCount + ", expected: " + fileCount); if (fileCount == 0) { theArraysLength = buf.getInt(); arrays = new int[theArraysLength][]; } while (buf.hasRemaining()) { int length = buf.getInt(); int[] array = new int[length]; arrays[arrayNumber++] = array; array[0] = length; for (int i = 1; i < length; i++) array[i] = buf.getInt(); // System.err.println("Got: "+Basic.toString(array,",")); } ins.close(); fileCount++; } return arrays; } private static void normalToFile(String fileName, int[][] arrays) throws IOException { try (OutputStream outs = new BufferedOutputStream(new FileOutputStream(fileName))) { outs.write(MAGIC_NUMBER, 0, MAGIC_NUMBER.length); byte[] buffer = new byte[8]; writeInt(outs, arrays.length, buffer); for (int[] array : arrays) { int length = array.length; writeInt(outs, length, buffer); for (int i = 1; i < length; i++) writeInt(outs, array[i], buffer); } } } private static int[][] normalFromFile(String fileName) throws IOException { InputStream ins = new BufferedInputStream(new FileInputStream(fileName)); byte[] theMagicNumber = new byte[MAGIC_NUMBER.length]; ins.read(theMagicNumber); byte[] buffer = new byte[8]; int theArraysLength = readInt(ins, buffer); int[][] arrays = new int[theArraysLength][]; for (int i = 0; i < theArraysLength; i++) { int length = readInt(ins, buffer); int[] array = new int[length]; arrays[i] = array; for (int j = 1; j < length; j++) array[j] = readInt(ins, buffer); } ins.close(); return arrays; } private static int[][] createArrays() { if (true) { Random random = new Random(666); int[][] arrays = new int[50000][]; for (int i = 0; i < arrays.length; i++) { arrays[i] = new int[random.nextInt(1000) + 1]; arrays[i][0] = arrays[i].length; for (int j = 1; j < arrays[i].length; j++) arrays[i][j] = random.nextInt(100); } return arrays; } else { int[][] arrays = new int[10][]; for (int i = 0; i < arrays.length; i++) { int length = i + 1; int[] array = new int[length]; arrays[i] = array; array[0] = length; for (int j = 1; j < length; j++) array[j] = j; } return arrays; } } /** * writes an int value * * @param outs * @param value * @param bytes * @throws java.io.IOException */ public static void writeInt(OutputStream outs, int value, byte[] bytes) throws IOException { bytes[0] = (byte) (value >> 24); bytes[1] = (byte) (value >> 16); bytes[2] = (byte) (value >> 8); bytes[3] = (byte) value; outs.write(bytes, 0, 4); } /** * read an int from an input stream * * @param ins * @param bytes * @return long value * @throws java.io.IOException */ public static int readInt(InputStream ins, byte[] bytes) throws IOException { if (ins.read(bytes, 0, 4) != 4) throw new IOException("Read int: too few bytes"); return ((bytes[0] & 0xFF) << 24) | ((bytes[1] & 0xFF) << 16) | ((bytes[2] & 0xFF) << 8) | (bytes[3] & 0xFF); } /** * replace the suffix of a file * * @param fileName * @param newSuffix * @return new file name */ public static String replaceFileSuffix(String fileName, String newSuffix) { return replaceFileSuffix(new File(fileName), newSuffix).getPath(); } /** * replace the suffix of a file * * @param file * @param newSuffix * @return new file */ public static File replaceFileSuffix(File file, String newSuffix) { String name = getFileBaseName(file.getName()); if (!name.endsWith(newSuffix)) name = name + (newSuffix != null ? newSuffix : ""); return new File(file.getParent(), name); } /** * returns name without any .suffix removed * * @param name * @return name without .suffix */ public static String getFileBaseName(String name) { { if (name != null) { int pos = name.lastIndexOf("."); if (pos > 0) name = name.substring(0, pos); } } return name; } } malt-0.5.2/src/malt/Version.java000066400000000000000000000020331400455127600164710ustar00rootroot00000000000000/* * Version.java Copyright (C) 2020. Daniel H. Huson * * (Some files contain contributions from other authors, who are then mentioned separately.) * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * */ package malt; /** * Malt version string * Daniel Huson, 2014 */ public class Version { public static final String NAME = "MALT"; public static final String SHORT_DESCRIPTION = "MALT (version 0.5.2, built 28 Jan 2021)"; } malt-0.5.2/src/malt/align/000077500000000000000000000000001400455127600152755ustar00rootroot00000000000000malt-0.5.2/src/malt/align/AlignerOptions.java000066400000000000000000000133701400455127600211010ustar00rootroot00000000000000/* * AlignerOptions.java Copyright (C) 2020. Daniel H. Huson * * (Some files contain contributions from other authors, who are then mentioned separately.) * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * */ package malt.align; import jloda.util.BlastMode; import jloda.util.Pair; /** * all options required by an aligner * Daniel Huson, 8.2014 */ public class AlignerOptions { public enum AlignmentMode {Local, SemiGlobal} private AlignmentMode alignmentType = AlignmentMode.Local; private int minSeedIdentities = 0; private int ungappedXDrop = 0; private int ungappedMinRawScore = 0; private int gapOpenPenalty = 7; private int gapExtensionPenalty = 3; private int matchScore = 2; private int mismatchScore = -3; private int band = 4; private boolean referenceIsDNA = true; // two values for computing blast statistics: private double lambda = 0.625; private double lnK = -0.89159811928378356416921953633132; private IScoringMatrix scoringMatrix; private long referenceDatabaseLength = 100000; private boolean samSoftClipping = false; public AlignmentMode getAlignmentType() { return alignmentType; } public void setAlignmentType(AlignmentMode alignmentType) { this.alignmentType = alignmentType; } public void setAlignmentType(String alignmentType) { setAlignmentType(AlignmentMode.valueOf(alignmentType)); } public int getGapOpenPenalty() { return gapOpenPenalty; } public void setGapOpenPenalty(int gapOpenPenalty) { this.gapOpenPenalty = gapOpenPenalty; } public int getGapExtensionPenalty() { return gapExtensionPenalty; } public void setGapExtensionPenalty(int gapExtensionPenalty) { this.gapExtensionPenalty = gapExtensionPenalty; } public int getMatchScore() { return matchScore; } public void setMatchScore(int matchScore) { this.matchScore = matchScore; } public int getMismatchScore() { return mismatchScore; } public void setMismatchScore(int mismatchScore) { this.mismatchScore = mismatchScore; } public int getBand() { return band; } public void setBand(int band) { this.band = band; } public long getReferenceDatabaseLength() { return referenceDatabaseLength; } public void setReferenceDatabaseLength(long referenceDatabaseLength) { this.referenceDatabaseLength = referenceDatabaseLength; } public IScoringMatrix getScoringMatrix() { return scoringMatrix; } public void setScoringMatrix(IScoringMatrix scoringMatrix) { this.scoringMatrix = scoringMatrix; } public void setLambdaAndK(Pair lambdaAndK) { System.err.println("BLAST statistics parameters: lambda=" + lambdaAndK.get1() + " k=" + lambdaAndK.get2()); lambda = lambdaAndK.get1(); lnK = Math.log(lambdaAndK.get2()); } public void setK(double K) { this.lnK = Math.log(K); } public double getK() { return Math.exp(lnK); } public void setLambda(double lambda) { this.lambda = lambda; } public double getLambda() { return lambda; } public double getLnK() { return lnK; } public boolean isReferenceIsDNA() { return referenceIsDNA; } public void setReferenceIsDNA(boolean referenceIsDNA) { this.referenceIsDNA = referenceIsDNA; } public int getMinSeedIdentities(final BlastMode mode) { if (minSeedIdentities == 0) { switch (mode) { case BlastP: case BlastX: return 10; case BlastN: return 0; // no need to set this, because BlastN seeds are always completely identical } } return minSeedIdentities; } public void setMinSeedIdentities(int minSeedIdentities) { this.minSeedIdentities = minSeedIdentities; } public int getUngappedXDrop(final BlastMode mode) { if (ungappedXDrop == 0) { switch (mode) { case BlastP: case BlastX: return 20; case BlastN: return 8; // todo: need to figure out best default } } return ungappedXDrop; } public void setUngappedXDrop(int ungappedXDrop) { this.ungappedXDrop = ungappedXDrop; } public int getUngappedMinRawScore(final BlastMode mode) { if (ungappedMinRawScore == 0) { switch (mode) { case BlastP: case BlastX: return 60; case BlastN: return 60; // todo: need to figure out best default } } return ungappedMinRawScore; } public void setUngappedMinRawScore(int ungappedMinRawScore) { this.ungappedMinRawScore = ungappedMinRawScore; } public boolean isSamSoftClipping() { return samSoftClipping; } public void setSamSoftClipping(boolean samSoftClipping) { this.samSoftClipping = samSoftClipping; } } malt-0.5.2/src/malt/align/BandedAligner.java000066400000000000000000001633501400455127600206270ustar00rootroot00000000000000/* * BandedAligner.java Copyright (C) 2020. Daniel H. Huson * * (Some files contain contributions from other authors, who are then mentioned separately.) * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * */ package malt.align; import jloda.util.Basic; import jloda.util.BlastMode; import jloda.util.ReusableByteBuffer; import malt.DataForInnerLoop; import malt.data.DNA5; import malt.io.SAMHelper; import malt.util.Utilities; /** * banded DNA aligner. Does both local and semiGlobal alignment * Daniel Huson, 8.2014 */ public class BandedAligner { private double lambda = 0.625; private double lnK = -0.89159811928378356416921953633132; private final static double LN_2 = 0.69314718055994530941723212145818; private final static int MINUS_INFINITY = -100000000; public static final int ALIGNMENT_SEGMENT_LENGTH = 60; // length of alignment segment in text format output private final static byte[] MID_TRACK_LEADING_SPACES = " ".getBytes(); // spaces used in text format output private long referenceDatabaseLength = 10000000; private byte[] query; private int queryLength; private byte[] reference; private int referenceLength; private final int[][] scoringMatrix; private final int gapOpenPenalty; private final int gapExtensionPenalty; private final int band; private int rawScore; private float bitScore = 0; private double expected = 0; private final boolean isDNAAlignment; private int identities; private int mismatches; private int gaps; private int gapOpens; private int alignmentLength; private final BlastMode mode; private final boolean doSemiGlobal; private int refOffset; // needed convert from row to position in reference private int startQuery; // first alignment position of query private int endQuery = -1; // last alignment position of query +1 private int startReference; private int endReference; private int[][] matrixM; private int[][] matrixIRef; private int[][] matrixIQuery; private byte[][] traceBackM; private byte[][] traceBackIRef; private byte[][] traceBackIQuery; private static final byte DONE = 9; private static final byte M_FROM_M = 1; private static final byte M_FROM_IRef = 2; private static final byte M_FROM_IQuery = 3; private static final byte IRef_FROM_M = 4; private static final byte IRef_FROM_IRef = 5; private static final byte IQuery_FROM_M = 6; private static final byte IQuery_FROM_IQuery = 7; // buffers: private byte[] queryTrack = new byte[1000]; private byte[] midTrack = new byte[1000]; private byte[] referenceTrack = new byte[1000]; private final ReusableByteBuffer alignmentBuffer = new ReusableByteBuffer(10000); private int queryPos; private int refPos; private final boolean samSoftClipping; // new stuff: private byte[][] alignment; // last computed alignment private int seedLength; // number of rows depends only on band width private final int rows; private final int lastRowToFill; private final int middleRow; /** * constructor * * @param alignerOptions */ public BandedAligner(final AlignerOptions alignerOptions, final BlastMode mode) { this.scoringMatrix = alignerOptions.getScoringMatrix().getMatrix(); this.isDNAAlignment = (mode == BlastMode.BlastN); this.doSemiGlobal = alignerOptions.getAlignmentType() == AlignerOptions.AlignmentMode.SemiGlobal; this.lambda = alignerOptions.getLambda(); this.lnK = alignerOptions.getLnK(); this.mode = mode; band = alignerOptions.getBand(); gapOpenPenalty = alignerOptions.getGapOpenPenalty(); gapExtensionPenalty = alignerOptions.getGapExtensionPenalty(); referenceDatabaseLength = alignerOptions.getReferenceDatabaseLength(); rows = 2 * band + 3; lastRowToFill = rows - 2; middleRow = rows / 2; // half matrixM = new int[0][0]; // don't init here, need to initialize properly matrixIRef = new int[0][0]; matrixIQuery = new int[0][0]; traceBackM = new byte[0][0]; traceBackIRef = new byte[0][0]; traceBackIQuery = new byte[0][0]; // todo: only use one traceback matrix samSoftClipping = alignerOptions.isSamSoftClipping(); } /** * Computes a banded local or semiGlobal alignment. * The raw score is computed. * * @param query * @param queryLength * @param reference * @param referenceLength * @param queryPos * @param refPos */ public void computeAlignment(byte[] query, int queryLength, byte[] reference, int referenceLength, int queryPos, int refPos, int seedLength) { this.query = query; this.queryLength = queryLength; this.reference = reference; this.referenceLength = referenceLength; this.queryPos = queryPos; this.refPos = refPos; this.seedLength = seedLength; startQuery = startReference = endQuery = endReference = -1; if (doSemiGlobal) computeSemiGlobalAlignment(); else computeLocalAlignment(); } /** * Performs a banded local alignment and return the raw score. */ private void computeLocalAlignment() { alignment = null; // will need to call alignmentByTraceBack to compute this refOffset = refPos - queryPos - band - 2; // need this to compute index in reference sequence final int cols = queryLength + 2; // query plus one col before and one after final int firstSeedCol = queryPos + 1; // +1 because col=pos+1 final int lastSeedCol = queryPos + seedLength; // +1 because col=pos+1, but then -1 because want to be last in seed (not first after seed) //if (lastSeedCol > queryLength) // return; // too long // ------- compute score that comes from seed (without first and last member) rawScore = 0; { for (int col = firstSeedCol + 1; col < lastSeedCol; col++) { final int refIndex = middleRow + col + refOffset; rawScore += scoringMatrix[query[col - 1]][reference[refIndex]]; } if (rawScore <= 0) { rawScore = 0; return; } } // ------- resize matrices if necessary: if (cols >= matrixM.length) { // all values will be 0 // resize: matrixM = new int[cols][rows]; matrixIRef = new int[cols][rows]; matrixIQuery = new int[cols][rows]; traceBackM = new byte[cols][rows]; traceBackIRef = new byte[cols][rows]; traceBackIQuery = new byte[cols][rows]; // initialize first column: for (int r = 1; r < rows; r++) { // matrixM[0][r] = matrixIRef[0][r] = matrixIQuery[0][r] = 0; traceBackM[0][r] = traceBackIRef[0][r] = traceBackIQuery[0][r] = DONE; } // initialize the first and last row: for (int c = 0; c < cols; c++) { // matrixM[c][0] = matrixIRef[c][0] = matrixIQuery[c][0] = matrixM[c][rows - 1] = matrixIRef[c][rows - 1] = matrixIQuery[c][rows - 1] = 0; traceBackM[c][0] = traceBackIRef[c][0] = traceBackIQuery[c][0] = traceBackM[c][rows - 1] = traceBackIRef[0][rows - 1] = traceBackIQuery[0][rows - 1] = DONE; } } // ------- fill dynamic programming matrix from 0 to first column of seed: { final int firstCol = Math.max(1, -refOffset - 2 * band - 1); // the column for which refIndex(firstCol,bottom-to-last row)==0 if (firstCol > 1) { final int prevCol = firstCol - 1; final int secondToLastRow = rows - 2; traceBackM[prevCol][secondToLastRow] = traceBackIRef[prevCol][secondToLastRow] = traceBackIQuery[prevCol][secondToLastRow] = DONE; // set previous column to done matrixM[prevCol][secondToLastRow] = matrixIRef[prevCol][secondToLastRow] = matrixIQuery[prevCol][secondToLastRow] = 0; } // note that query pos is c-1, because c==0 is before start of query for (int col = firstCol; col <= firstSeedCol; col++) { // we never modify the first column or the first or last row for (int row = 1; row <= lastRowToFill; row++) { final int refIndex = row + col + refOffset; if (refIndex == -1) { // in column before reference starts, init traceBackM[col][row] = traceBackIRef[col][row] = traceBackIQuery[col][row] = DONE; matrixM[col][row] = matrixIRef[col][row] = matrixIQuery[col][row] = 0; } else if (refIndex >= 0 && refIndex < reference.length) //do the actual alignment: { int bestMScore = 0; // match or mismatch { final int s = scoringMatrix[query[col - 1]][reference[refIndex]]; int score = matrixM[col - 1][row] + s; if (score > 0) { traceBackM[col][row] = M_FROM_M; bestMScore = score; } score = matrixIRef[col - 1][row] + s; if (score > bestMScore) { traceBackM[col][row] = M_FROM_IRef; bestMScore = score; } score = matrixIQuery[col - 1][row] + s; if (score > bestMScore) { traceBackM[col][row] = M_FROM_IQuery; bestMScore = score; } if (bestMScore == 0) { traceBackM[col][row] = DONE; } matrixM[col][row] = bestMScore; } // insertion in reference: int bestIRefScore = 0; { int score = matrixM[col][row - 1] - gapOpenPenalty; if (score > bestIRefScore) { traceBackIRef[col][row] = IRef_FROM_M; bestIRefScore = score; } score = matrixIRef[col][row - 1] - gapExtensionPenalty; if (score > bestIRefScore) { bestIRefScore = score; traceBackIRef[col][row] = IRef_FROM_IRef; } if (bestIRefScore == 0) { traceBackIRef[col][row] = DONE; } matrixIRef[col][row] = bestIRefScore; } // insertion in query: int bestIQueryScore = 0; { int score = matrixM[col - 1][row + 1] - gapOpenPenalty; if (score > bestIQueryScore) { bestIQueryScore = score; traceBackIQuery[col][row] = IQuery_FROM_M; } score = matrixIQuery[col - 1][row + 1] - gapExtensionPenalty; if (score > bestIQueryScore) { bestIQueryScore = score; traceBackIQuery[col][row] = IQuery_FROM_IQuery; } if (bestIQueryScore == 0) { traceBackIQuery[col][row] = DONE; } matrixIQuery[col][row] = bestIQueryScore; } } // else refIndex < -1 } } } // ------- fill dynamic programming matrix from end of query to last column of seed: { final int lastCol = Math.min(queryLength + 1, queryPos + referenceLength - refPos + 1); // last column, fill upto lastCol-1 // initial last column: for (int row = 1; row < rows; row++) { matrixM[lastCol][row] = matrixIRef[lastCol][row] = matrixIQuery[lastCol][row] = 0; traceBackM[lastCol][row] = traceBackIRef[lastCol][row] = traceBackIQuery[lastCol][row] = DONE; } // note that col=pos-1, or pos=col+1, because c==0 is before start of query /* System.err.println("lastSeedCol: " + lastSeedCol); System.err.println("lastCol: " + lastCol); System.err.println("lastRowToFill: " + lastRowToFill); */ for (int col = lastCol - 1; col >= lastSeedCol; col--) { // we never modify the first column or the first or last row for (int row = lastRowToFill; row >= 1; row--) { final int refIndex = row + col + refOffset; if (refIndex >= referenceLength) { // out of range of the alignment traceBackM[col][row] = traceBackIRef[col][row] = traceBackIQuery[col][row] = DONE; matrixM[col][row] = matrixIRef[col][row] = matrixIQuery[col][row] = 0; } else if (refIndex >= 0 && refIndex < referenceLength) { // do the actual alignment: int bestMScore = 0; // match or mismatch { final int s = scoringMatrix[query[col - 1]][reference[refIndex]]; // pos in query=col-1 int score = matrixM[col + 1][row] + s; if (score > 0) { traceBackM[col][row] = M_FROM_M; bestMScore = score; } score = matrixIRef[col + 1][row] + s; if (score > bestMScore) { traceBackM[col][row] = M_FROM_IRef; bestMScore = score; } score = matrixIQuery[col + 1][row] + s; if (score > bestMScore) { traceBackM[col][row] = M_FROM_IQuery; bestMScore = score; } if (bestMScore == 0) { traceBackM[col][row] = DONE; } matrixM[col][row] = bestMScore; } // insertion in ref int bestIRefScore = 0; { int score = matrixM[col][row + 1] - gapOpenPenalty; if (score > bestIRefScore) { traceBackIRef[col][row] = IRef_FROM_M; bestIRefScore = score; } score = matrixIRef[col][row + 1] - gapExtensionPenalty; if (score > bestIRefScore) { bestIRefScore = score; traceBackIRef[col][row] = IRef_FROM_IRef; } if (bestIRefScore == 0) { traceBackIRef[col][row] = DONE; } matrixIRef[col][row] = bestIRefScore; } // insertion in query: int bestIQueryScore = 0; { int score = matrixM[col + 1][row - 1] - gapOpenPenalty; if (score > bestIQueryScore) { bestIQueryScore = score; traceBackIQuery[col][row] = IQuery_FROM_M; } score = matrixIQuery[col + 1][row - 1] - gapExtensionPenalty; if (score > bestIQueryScore) { bestIQueryScore = score; traceBackIQuery[col][row] = IQuery_FROM_IQuery; } if (bestIQueryScore == 0) { traceBackIQuery[col][row] = DONE; } matrixIQuery[col][row] = bestIQueryScore; } } // else refIndex >referenceLength } } } if (false) { { System.err.println("queryPos: " + queryPos); System.err.println("refPos: " + refPos); System.err.println("seedLen.: " + seedLength); System.err.println("Query:"); System.err.println(Basic.toString(query)); System.err.println("Reference:"); System.err.println(Basic.toString(reference)); } { System.err.println("SeedScore: " + rawScore); int firstScore = Math.max(Math.max(matrixIQuery[firstSeedCol][middleRow], matrixIRef[firstSeedCol][middleRow]), matrixM[firstSeedCol][middleRow]); System.err.println("FirstScore: " + firstScore); int secondScore = Math.max(Math.max(matrixIQuery[lastSeedCol][middleRow], matrixIRef[lastSeedCol][middleRow]), matrixM[lastSeedCol][middleRow]); System.err.println("secondScore: " + secondScore); System.err.println("totalScore: " + (rawScore + firstScore + secondScore)); } { System.err.println("Matrix M:"); System.err.println(toString(matrixM, 0, cols, query)); System.err.println("Matrix IQuery:"); System.err.println(toString(matrixIQuery, 0, cols, query)); System.err.println("Matrix IRef:"); System.err.println(toString(matrixIRef, 0, cols, query)); } } rawScore += Math.max(Math.max(matrixIQuery[firstSeedCol][middleRow], matrixIRef[firstSeedCol][middleRow]), matrixM[firstSeedCol][middleRow]); rawScore += Math.max(Math.max(matrixIQuery[lastSeedCol][middleRow], matrixIRef[lastSeedCol][middleRow]), matrixM[lastSeedCol][middleRow]); } /** * Performs a banded semi-global alignment. */ private void computeSemiGlobalAlignment() { alignment = null; // will need to call alignmentByTraceBack to compute this refOffset = refPos - queryPos - band - 2; // need this to compute index in reference sequence final int cols = queryLength + 2; // query plus one col before and one after final int firstSeedCol = queryPos + 1; // +1 because col=pos+1 final int lastSeedCol = queryPos + seedLength; // +1 because col=pos+1, but then -1 because want to be last in seed (not first after seed) //if (lastSeedCol > queryLength) // return; // too long // ------- compute score that comes from seed (without first and last member) rawScore = 0; { for (int col = firstSeedCol + 1; col < lastSeedCol; col++) { final int refIndex = middleRow + col + refOffset; rawScore += scoringMatrix[query[col - 1]][reference[refIndex]]; } if (rawScore <= 0) { rawScore = 0; return; } } // ------- resize matrices if necessary: if (cols >= matrixM.length) { // all values will be 0 // resize: matrixM = new int[cols][rows]; matrixIRef = new int[cols][rows]; matrixIQuery = new int[cols][rows]; traceBackM = new byte[cols][rows]; traceBackIRef = new byte[cols][rows]; traceBackIQuery = new byte[cols][rows]; // initialize first column: for (int r = 1; r < rows; r++) { traceBackM[0][r] = traceBackIRef[0][r] = traceBackIQuery[0][r] = DONE; matrixIQuery[0][r] = -gapOpenPenalty; } // initialize the first and last row: for (int c = 0; c < cols; c++) { matrixM[c][0] = matrixIRef[c][0] = matrixIQuery[c][0] = matrixM[c][rows - 1] = matrixIRef[c][rows - 1] = matrixIQuery[c][rows - 1] = MINUS_INFINITY; // must never go outside band } } // ------- fill dynamic programming matrix from 0 to first column of seed: { final int firstCol = Math.max(1, -refOffset - 2 * band - 1); // the column for which refIndex(firstCol,bottom-to-last row)==0 if (firstCol > 1) { final int prevCol = firstCol - 1; final int secondToLastRow = rows - 2; traceBackM[prevCol][secondToLastRow] = traceBackIRef[prevCol][secondToLastRow] = traceBackIQuery[prevCol][secondToLastRow] = DONE; // set previous column to done matrixM[prevCol][secondToLastRow] = matrixIRef[prevCol][secondToLastRow] = matrixIQuery[prevCol][secondToLastRow] = 0; } // note that query pos is c-1, because c==0 is before start of query for (int col = firstCol; col <= firstSeedCol; col++) { // we never modify the first column or the first or last row for (int row = 1; row <= lastRowToFill; row++) { final int refIndex = row + col + refOffset; if (refIndex >= reference.length) continue; // todo: debug this, sometimes happens, but shouldn't if (refIndex == -1) { // in column before reference starts, init traceBackM[col][row] = traceBackIRef[col][row] = traceBackIQuery[col][row] = DONE; matrixM[col][row] = 0; matrixIRef[col][row] = matrixIQuery[col][row] = -gapOpenPenalty; } else if (refIndex >= 0) //do the actual alignment: { int bestMScore = Integer.MIN_VALUE; // match or mismatch { final int s = scoringMatrix[query[col - 1]][reference[refIndex]]; int score = matrixM[col - 1][row] + s; if (score > bestMScore) { traceBackM[col][row] = M_FROM_M; bestMScore = score; } score = matrixIRef[col - 1][row] + s; if (score > bestMScore) { traceBackM[col][row] = M_FROM_IRef; bestMScore = score; } score = matrixIQuery[col - 1][row] + s; if (score > bestMScore) { traceBackM[col][row] = M_FROM_IQuery; bestMScore = score; } matrixM[col][row] = bestMScore; } // insertion in reference: int bestIRefScore = Integer.MIN_VALUE; { int score = matrixM[col][row - 1] - gapOpenPenalty; if (score > bestIRefScore) { traceBackIRef[col][row] = IRef_FROM_M; bestIRefScore = score; } score = matrixIRef[col][row - 1] - gapExtensionPenalty; if (score > bestIRefScore) { bestIRefScore = score; traceBackIRef[col][row] = IRef_FROM_IRef; } matrixIRef[col][row] = bestIRefScore; } // insertion in query: int bestIQueryScore = Integer.MIN_VALUE; { int score = matrixM[col - 1][row + 1] - gapOpenPenalty; if (score > bestIQueryScore) { bestIQueryScore = score; traceBackIQuery[col][row] = IQuery_FROM_M; } score = matrixIQuery[col - 1][row + 1] - gapExtensionPenalty; if (score > bestIQueryScore) { bestIQueryScore = score; traceBackIQuery[col][row] = IQuery_FROM_IQuery; } matrixIQuery[col][row] = bestIQueryScore; } } // else refIndex < -1 } } } // ------- fill dynamic programming matrix from end of query to last column of seed: { final int lastCol = Math.min(queryLength + 1, queryPos + referenceLength - refPos + 1); // last column, fill upto lastCol-1 // initial last column: for (int row = 1; row < rows - 1; row++) { // no need to init first or last row... matrixM[lastCol][row] = 0; matrixIRef[lastCol][row] = matrixIQuery[lastCol][row] = -gapOpenPenalty; traceBackM[lastCol][row] = traceBackIRef[lastCol][row] = traceBackIQuery[lastCol][row] = DONE; } // note that col=pos-1, or pos=col+1, because c==0 is before start of query /* System.err.println("lastSeedCol: " + lastSeedCol); System.err.println("lastCol: " + lastCol); System.err.println("lastRowToFill: " + lastRowToFill); */ for (int col = lastCol - 1; col >= lastSeedCol; col--) { // we never modify the first column or the first or last row for (int row = lastRowToFill; row >= 1; row--) { final int refIndex = row + col + refOffset; if (refIndex >= referenceLength) { // out of range of the alignment traceBackM[col][row] = traceBackIRef[col][row] = traceBackIQuery[col][row] = DONE; matrixM[col][row] = matrixIRef[col][row] = matrixIQuery[col][row] = -gapOpenPenalty; } else if (refIndex >= 0 && refIndex < referenceLength) { // do the actual alignment: int bestMScore = Integer.MIN_VALUE; // match or mismatch { final int s = scoringMatrix[query[col - 1]][reference[refIndex]]; // pos in query=col-1 int score = matrixM[col + 1][row] + s; if (score > bestMScore) { traceBackM[col][row] = M_FROM_M; bestMScore = score; } score = matrixIRef[col + 1][row] + s; if (score > bestMScore) { traceBackM[col][row] = M_FROM_IRef; bestMScore = score; } score = matrixIQuery[col + 1][row] + s; if (score > bestMScore) { traceBackM[col][row] = M_FROM_IQuery; bestMScore = score; } matrixM[col][row] = bestMScore; } // insertion in ref int bestIRefScore = Integer.MIN_VALUE; { int score = matrixM[col][row + 1] - gapOpenPenalty; if (score > bestIRefScore) { traceBackIRef[col][row] = IRef_FROM_M; bestIRefScore = score; } score = matrixIRef[col][row + 1] - gapExtensionPenalty; if (score > bestIRefScore) { bestIRefScore = score; traceBackIRef[col][row] = IRef_FROM_IRef; } matrixIRef[col][row] = bestIRefScore; } // insertion in query: int bestIQueryScore = Integer.MIN_VALUE; { int score = matrixM[col + 1][row - 1] - gapOpenPenalty; if (score > bestIQueryScore) { bestIQueryScore = score; traceBackIQuery[col][row] = IQuery_FROM_M; } score = matrixIQuery[col + 1][row - 1] - gapExtensionPenalty; if (score > bestIQueryScore) { bestIQueryScore = score; traceBackIQuery[col][row] = IQuery_FROM_IQuery; } matrixIQuery[col][row] = bestIQueryScore; } } // else refIndex >referenceLength } } } if (false) { { System.err.println("queryPos: " + queryPos); System.err.println("refPos: " + refPos); System.err.println("seedLen.: " + seedLength); System.err.println("Query:"); System.err.println(Basic.toString(query)); System.err.println("Reference:"); System.err.println(Basic.toString(reference)); } { System.err.println("SeedScore: " + rawScore); int firstScore = Math.max(Math.max(matrixIQuery[firstSeedCol][middleRow], matrixIRef[firstSeedCol][middleRow]), matrixM[firstSeedCol][middleRow]); System.err.println("FirstScore: " + firstScore); int secondScore = Math.max(Math.max(matrixIQuery[lastSeedCol][middleRow], matrixIRef[lastSeedCol][middleRow]), matrixM[lastSeedCol][middleRow]); System.err.println("secondScore: " + secondScore); System.err.println("totalScore: " + (rawScore + firstScore + secondScore)); } { System.err.println("Matrix M:"); System.err.println(toString(matrixM, 0, cols, query)); System.err.println("Matrix IQuery:"); System.err.println(toString(matrixIQuery, 0, cols, query)); System.err.println("Matrix IRef:"); System.err.println(toString(matrixIRef, 0, cols, query)); } } rawScore += Math.max(Math.max(matrixIQuery[firstSeedCol][middleRow], matrixIRef[firstSeedCol][middleRow]), matrixM[firstSeedCol][middleRow]); rawScore += Math.max(Math.max(matrixIQuery[lastSeedCol][middleRow], matrixIRef[lastSeedCol][middleRow]), matrixM[lastSeedCol][middleRow]); } /** * compute the bit score and expected score from the raw score */ public void computeBitScoreAndExpected() { if (rawScore > 0) { bitScore = (float) ((lambda * rawScore - lnK) / LN_2); expected = referenceDatabaseLength * queryLength * Math.pow(2, -bitScore); } else { bitScore = 0; expected = Double.MAX_VALUE; } } /** * gets the alignment. Also sets the number of matches, mismatches and gaps * * @return alignment */ public void computeAlignmentByTraceBack() { if (rawScore <= 0) { alignment = null; return; } gaps = 0; gapOpens = 0; identities = 0; mismatches = 0; // get first part of alignment: int length = 0; { int r = middleRow; int c = queryPos + 1; byte[][] traceBack; traceBack = traceBackM; if (matrixIRef[c][r] > matrixM[c][r]) { traceBack = traceBackIRef; if (matrixIQuery[c][r] > matrixIRef[c][r]) traceBack = traceBackIQuery; } else if (matrixIQuery[c][r] > matrixM[c][r]) traceBack = traceBackIQuery; loop: while (true) { int refIndex = r + c + refOffset; switch (traceBack[c][r]) { case DONE: startQuery = c; startReference = r + c + refOffset + 1; break loop; case M_FROM_M: queryTrack[length] = query[c - 1]; referenceTrack[length] = reference[refIndex]; if (queryTrack[length] == referenceTrack[length]) { if (isDNAAlignment) midTrack[length] = '|'; else midTrack[length] = queryTrack[length]; identities++; } else { if (isDNAAlignment || scoringMatrix[queryTrack[length]][referenceTrack[length]] <= 0) midTrack[length] = ' '; else midTrack[length] = '+'; mismatches++; } c--; traceBack = traceBackM; break; case M_FROM_IRef: queryTrack[length] = query[c - 1]; referenceTrack[length] = reference[refIndex]; if (queryTrack[length] == referenceTrack[length]) { if (isDNAAlignment) midTrack[length] = '|'; else midTrack[length] = queryTrack[length]; identities++; } else { if (isDNAAlignment || scoringMatrix[queryTrack[length]][referenceTrack[length]] <= 0) midTrack[length] = ' '; else midTrack[length] = '+'; } c--; traceBack = traceBackIRef; break; case M_FROM_IQuery: queryTrack[length] = query[c - 1]; referenceTrack[length] = reference[refIndex]; if (queryTrack[length] == referenceTrack[length]) { if (isDNAAlignment) midTrack[length] = '|'; else midTrack[length] = queryTrack[length]; identities++; } else { if (isDNAAlignment || scoringMatrix[queryTrack[length]][referenceTrack[length]] <= 0) midTrack[length] = ' '; else midTrack[length] = '+'; } c--; traceBack = traceBackIQuery; break; case IRef_FROM_M: queryTrack[length] = '-'; referenceTrack[length] = reference[refIndex]; midTrack[length] = ' '; r--; traceBack = traceBackM; gaps++; gapOpens++; break; case IRef_FROM_IRef: queryTrack[length] = '-'; referenceTrack[length] = reference[refIndex]; midTrack[length] = ' '; r--; traceBack = traceBackIRef; gaps++; break; case IQuery_FROM_M: queryTrack[length] = query[c - 1]; referenceTrack[length] = '-'; midTrack[length] = ' '; c--; r++; traceBack = traceBackM; gaps++; gapOpens++; break; case IQuery_FROM_IQuery: queryTrack[length] = query[c - 1]; referenceTrack[length] = '-'; midTrack[length] = ' '; c--; r++; traceBack = traceBackIQuery; gaps++; break; default: throw new RuntimeException("Undefined trace-back state: " + traceBack[c][r]); } if (queryTrack[length] == '-' && referenceTrack[length] == '-') System.err.println("gap-gap at: " + length); if (++length >= queryTrack.length) { queryTrack = grow(queryTrack); midTrack = grow(midTrack); referenceTrack = grow(referenceTrack); } } // end of loop reverseInPlace(queryTrack, length); reverseInPlace(midTrack, length); reverseInPlace(referenceTrack, length); } // get second part of alignment: { for (int i = 1; i < seedLength - 1; i++) { queryTrack[length] = query[queryPos + i]; referenceTrack[length] = reference[refPos + i]; if (queryTrack[length] == referenceTrack[length]) { if (isDNAAlignment) midTrack[length] = '|'; else midTrack[length] = queryTrack[length]; identities++; } else { if (isDNAAlignment || scoringMatrix[queryTrack[length]][referenceTrack[length]] <= 0) midTrack[length] = ' '; else midTrack[length] = '+'; mismatches++; } if (++length >= queryTrack.length) { queryTrack = grow(queryTrack); midTrack = grow(midTrack); referenceTrack = grow(referenceTrack); } } } // get third part of alignment: { int r = middleRow; int c = queryPos + seedLength; // +1 because col=pos+1, but -1 because want to be in last position of seed byte[][] traceBack; traceBack = traceBackM; if (matrixIRef[c][r] > matrixM[c][r]) { traceBack = traceBackIRef; if (matrixIQuery[c][r] > matrixIRef[c][r]) traceBack = traceBackIQuery; } else if (matrixIQuery[c][r] > matrixM[c][r]) traceBack = traceBackIQuery; loop: while (true) { int refIndex = r + c + refOffset; switch (traceBack[c][r]) { case DONE: endQuery = c - 1; endReference = r + c + refOffset + 1; break loop; case M_FROM_M: queryTrack[length] = query[c - 1]; referenceTrack[length] = reference[refIndex]; if (queryTrack[length] == referenceTrack[length]) { if (isDNAAlignment) midTrack[length] = '|'; else midTrack[length] = queryTrack[length]; identities++; } else { if (isDNAAlignment || scoringMatrix[queryTrack[length]][referenceTrack[length]] <= 0) midTrack[length] = ' '; else midTrack[length] = '+'; mismatches++; } c++; traceBack = traceBackM; break; case M_FROM_IRef: queryTrack[length] = query[c - 1]; referenceTrack[length] = reference[refIndex]; if (queryTrack[length] == referenceTrack[length]) { if (isDNAAlignment) midTrack[length] = '|'; else midTrack[length] = queryTrack[length]; identities++; } else { if (isDNAAlignment || scoringMatrix[queryTrack[length]][referenceTrack[length]] <= 0) midTrack[length] = ' '; else midTrack[length] = '+'; } c++; traceBack = traceBackIRef; break; case M_FROM_IQuery: queryTrack[length] = query[c - 1]; referenceTrack[length] = reference[refIndex]; if (queryTrack[length] == referenceTrack[length]) { if (isDNAAlignment) midTrack[length] = '|'; else midTrack[length] = queryTrack[length]; identities++; } else { if (isDNAAlignment || scoringMatrix[queryTrack[length]][referenceTrack[length]] <= 0) midTrack[length] = ' '; else midTrack[length] = '+'; } c++; traceBack = traceBackIQuery; break; case IRef_FROM_M: queryTrack[length] = '-'; referenceTrack[length] = reference[refIndex]; midTrack[length] = ' '; r++; traceBack = traceBackM; gaps++; gapOpens++; break; case IRef_FROM_IRef: queryTrack[length] = '-'; referenceTrack[length] = reference[refIndex]; midTrack[length] = ' '; r++; traceBack = traceBackIRef; gaps++; break; case IQuery_FROM_M: queryTrack[length] = query[c - 1]; referenceTrack[length] = '-'; midTrack[length] = ' '; c++; r--; traceBack = traceBackM; gaps++; gapOpens++; break; case IQuery_FROM_IQuery: queryTrack[length] = query[c - 1]; referenceTrack[length] = '-'; midTrack[length] = ' '; c++; r--; traceBack = traceBackIQuery; gaps++; break; default: { throw new RuntimeException("Undefined trace-back state: " + traceBack[c][r]); } } if (queryTrack[length] == '-' && referenceTrack[length] == '-') System.err.println("gap-gap at: " + length); if (++length >= queryTrack.length) { queryTrack = grow(queryTrack); midTrack = grow(midTrack); referenceTrack = grow(referenceTrack); } } // end of loop } alignmentLength = length; alignment = new byte[][]{copy(queryTrack, length), copy(midTrack, length), copy(referenceTrack, length)}; } public int getStartQuery() { return startQuery; } public int getEndQuery() { return endQuery; } public int getStartReference() { return startReference; } public int getEndReference() { return endReference; } public int getGaps() { return gaps; } public int getGapOpens() { return gapOpens; } public int getIdentities() { return identities; } public float getPercentIdentity() { if (alignment == null) computeAlignmentByTraceBack(); return getAlignmentLength() == 0 ? 0 : (float) (100 * getIdentities()) / (float) getAlignmentLength(); } public int getMismatches() { return mismatches; } public int getRawScore() { return rawScore; } public float getBitScore() { return bitScore; } public double getExpected() { return expected; } public int getAlignmentLength() { return alignmentLength; } public long getReferenceDatabaseLength() { return referenceDatabaseLength; } public void setReferenceDatabaseLength(long referenceDatabaseLength) { this.referenceDatabaseLength = referenceDatabaseLength; } /** * reverse bytes * * @param array * @return reversed bytes */ private void reverseInPlace(byte[] array, int length) { int top = length / 2; for (int i = 0; i < top; i++) { byte tmp = array[i]; int j = length - i - 1; array[i] = array[j]; array[j] = tmp; } } /** * grow an array * * @param a * @return larger array containing values */ private byte[] grow(byte[] a) { byte[] result = new byte[Math.max(2, 2 * a.length)]; System.arraycopy(a, 0, result, 0, a.length); return result; } /** * return a copy * * @param array * @param length * @return copy */ public byte[] copy(byte[] array, int length) { byte[] result = new byte[length]; System.arraycopy(array, 0, result, 0, length); return result; } /** * return a reverse copy * * @param array * @param length * @return copy */ public byte[] copyReverse(byte[] array, int length) { byte[] result = new byte[length]; for (int i = 0; i < length; i++) result[i] = array[length - 1 - i]; return result; } /** * to string * * @param colRowMatrix * @return */ private String toString(int[][] colRowMatrix, int firstCol, int cols, byte[] query) { StringBuilder buf = new StringBuilder(); buf.append(" |"); for (int i = firstCol; i < cols; i++) { buf.append(String.format(" %3d", i)); } buf.append("\n"); buf.append(" | "); for (int i = firstCol + 1; i < cols; i++) { buf.append(" ").append((char) query[i - 1]); } buf.append("\n"); buf.append("---+"); buf.append("----".repeat(Math.max(0, cols - firstCol))); buf.append("\n"); int r = 0; boolean hasRow = true; while (hasRow) { hasRow = false; for (int i = firstCol; i < cols; i++) { int[] aColRowMatrix = colRowMatrix[i]; if (aColRowMatrix.length > r) { if (!hasRow) { hasRow = true; buf.append(String.format("%2d |", r)); } int value = aColRowMatrix[r]; if (value <= MINUS_INFINITY) buf.append(" -oo"); else buf.append(String.format(" %3d", value)); } } buf.append("\n"); r++; } return buf.toString(); } /** * gets the alignment text * * @param data * @return alignment text */ public byte[] getAlignmentText(DataForInnerLoop data, int frameRank) { if (alignment == null) computeAlignmentByTraceBack(); alignmentBuffer.reset(); if (getExpected() != 0) alignmentBuffer.writeAsAscii(String.format(" Score = %.1f bits (%d), Expect = %.1g\n", getBitScore(), getRawScore(), getExpected())); else alignmentBuffer.writeAsAscii(String.format(" Score = %.1f bits (%d), Expect = 0.0\n", getBitScore(), getRawScore())); if (isDNAAlignment) alignmentBuffer.writeAsAscii(String.format(" Identities = %d/%d (%.0f%%), Gaps = %d/%d (%.0f%%)\n", getIdentities(), getAlignmentLength(), (100.0 * (getIdentities()) / getAlignmentLength()), getGaps(), getAlignmentLength(), (100.0 * (getGaps()) / getAlignmentLength()))); else // protein alignment { int numberOfPositives = getAlignmentLength() - Basic.countOccurrences(alignment[1], ' '); alignmentBuffer.writeAsAscii(String.format(" Identities = %d/%d (%.0f%%), Positives = %d/%d (%.0f%%), Gaps = %d/%d (%.0f%%)\n", getIdentities(), getAlignmentLength(), (100.0 * (getIdentities()) / getAlignmentLength()), numberOfPositives, getAlignmentLength(), (100.0 * (numberOfPositives) / getAlignmentLength()), getGaps(), getAlignmentLength(), (100.0 * (getGaps()) / getAlignmentLength()))); } String frameInfo = data.getFrameInfoLine(frameRank); if (frameInfo != null) alignmentBuffer.writeAsAscii(frameInfo); int qFactor; if (mode == BlastMode.BlastN) qFactor = 1; else qFactor = 3; if (alignment != null) { int qStart = data.getStartQueryForOutput(frameRank, startQuery); int qDirection = (data.getEndQueryForOutput(frameRank, endQuery) - qStart >= 0 ? 1 : -1); int sStart = startReference + 1; for (int pos = 0; pos < alignment[0].length; pos += ALIGNMENT_SEGMENT_LENGTH) { int add = Math.min(ALIGNMENT_SEGMENT_LENGTH, alignment[0].length - pos); int qGaps = Utilities.countGaps(alignment[0], pos, add); int qEnd = qStart + qFactor * qDirection * ((add - qGaps) - 1); if (qFactor == 3) { qEnd += 2 * qDirection; } alignmentBuffer.writeAsAscii(String.format("\nQuery: %9d ", qStart)); alignmentBuffer.write(alignment[0], pos, add); alignmentBuffer.writeAsAscii(String.format(" %d\n", qEnd)); qStart = qEnd + qDirection; alignmentBuffer.write(MID_TRACK_LEADING_SPACES); alignmentBuffer.write(alignment[1], pos, add); int sGaps = Utilities.countGaps(alignment[2], pos, add); int sEnd = sStart + (add - sGaps) - 1; alignmentBuffer.writeAsAscii(String.format("\nSbjct: %9d ", sStart)); alignmentBuffer.write(alignment[2], pos, add); alignmentBuffer.writeAsAscii(String.format(" %d\n", sEnd)); sStart = sEnd + 1; } } return alignmentBuffer.makeCopy(); } /** * gets simple text, for debugging purproses * * @return text */ public byte[] getAlignmentSimpleText() { DataForInnerLoop dataForInnerLoop = new DataForInnerLoop(mode, true, false, 1, 1); return getAlignmentText(dataForInnerLoop, 0); } /** * get alignment in tabular format. If queryHeader==null, skips the first entry which is the query name * * @param data * @param queryHeader * @param referenceHeader * @param frameRank * @return tabular format without first field */ public byte[] getAlignmentTab(final DataForInnerLoop data, final byte[] queryHeader, final byte[] referenceHeader, final int frameRank) { if (alignment == null) computeAlignmentByTraceBack(); int outputStartQuery = data.getStartQueryForOutput(frameRank, startQuery); int outputEndQuery = data.getEndQueryForOutput(frameRank, endQuery); // queryId, subjectId, percIdentity, alnLength, mismatchCount, gapOpenCount, queryStart, queryEnd, subjectStart, subjectEnd, eVal, bitScore alignmentBuffer.reset(); if (queryHeader != null) { int length = Utilities.getFirstWordSkipLeadingGreaterSign(queryHeader, queryTrack); alignmentBuffer.write(queryTrack, 0, length); alignmentBuffer.write('\t'); } { int length = Utilities.getFirstWordSkipLeadingGreaterSign(referenceHeader, queryTrack); // misuse query track alignmentBuffer.write(queryTrack, 0, length); } alignmentBuffer.write('\t'); if (getExpected() == 0) alignmentBuffer.writeAsAscii(String.format("%.1f\t%d\t%d\t%d\t%d\t%d\t%d\t%d\t0.0\t%d", ((100.0 * getIdentities()) / getAlignmentLength()), getAlignmentLength(), getMismatches(), getGapOpens(), outputStartQuery, outputEndQuery, getStartReference() + 1, getEndReference(), Math.round(getBitScore()))); else alignmentBuffer.writeAsAscii(String.format("%.1f\t%d\t%d\t%d\t%d\t%d\t%d\t%d\t%.1g\t%d", ((100.0 * getIdentities()) / getAlignmentLength()), getAlignmentLength(), getMismatches(), getGapOpens(), outputStartQuery, outputEndQuery, getStartReference() + 1, getEndReference(), getExpected(), Math.round(getBitScore()))); return alignmentBuffer.makeCopy(); } /** * get alignment in SAM format * * @param queryHeader * @param referenceHeader * @param frameRank * @return SAM line */ public byte[] getAlignmentSAM(final DataForInnerLoop data, final byte[] queryHeader, final byte[] querySequence, final byte[] referenceHeader, final int frameRank) { if (alignment == null) computeAlignmentByTraceBack(); final int frame = data.getFrameForFrameRank(frameRank); final boolean queryIsReverseComplemented = isDNAAlignment && frame < 0; final int outputStartReference; final int outputEndReference; if (queryIsReverseComplemented) { outputStartReference = endReference + 1; outputEndReference = startReference + 1; DNA5.getInstance().reverseComplement(alignment[0]); DNA5.getInstance().reverse(alignment[1]); DNA5.getInstance().reverseComplement(alignment[2]); } else { outputStartReference = startReference + 1; outputEndReference = endReference; } int blastXQueryStart = 0; if (mode == BlastMode.BlastX) { blastXQueryStart = data.getStartQueryForOutput(frameRank, startQuery); } return SAMHelper.createSAMLine(mode, queryHeader, querySequence, startQuery, blastXQueryStart, endQuery, queryLength, alignment[0], referenceHeader, outputStartReference, outputEndReference, alignment[2], referenceLength, bitScore, rawScore, expected, 100 * identities / alignmentLength, frame, data.getQualityValues(), samSoftClipping).getBytes(); } /** * maps a bit score to a raw score * * @param bitScore * @return raw score */ public int getRawScoreForBitScore(double bitScore) { return (int) Math.floor((LN_2 * bitScore + lnK) / lambda); } private static final int minNumberOfExactMatches = 10; private static final int windowForMinNumberOfExactMatches = 30; /** * heuristically check whether there is going to be a good alignment * * @param query * @param reference * @param queryPos * @param refPos * @return true, if good alignment is likely */ public boolean quickCheck(final byte[] query, final int queryLength, final byte[] reference, final int referenceLength, final int queryPos, final int refPos) { if (mode == BlastMode.BlastN) return true; if (queryPos + minNumberOfExactMatches >= queryLength || refPos + minNumberOfExactMatches >= referenceLength) return false; int count = 0; final int maxSteps = Math.min(windowForMinNumberOfExactMatches, Math.min(queryLength - queryPos, referenceLength - refPos)); for (int i = 0; i < maxSteps; i++) { if (query[queryPos + i] == reference[refPos + i]) { count++; if (count == minNumberOfExactMatches) return true; } } return false; } } malt-0.5.2/src/malt/align/BlastStatisticsHelper.java000066400000000000000000000175251400455127600224320ustar00rootroot00000000000000/* * BlastStatisticsHelper.java Copyright (C) 2020. Daniel H. Huson * * (Some files contain contributions from other authors, who are then mentioned separately.) * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * */ package malt.align; import jloda.util.Basic; import jloda.util.Pair; import java.io.IOException; /** * blast statistics helper * Daniel Huson, 8.2014 */ public class BlastStatisticsHelper { private long referenceLength; private final double lnK; private final double k; private final double lambda; private final double LN2 = (float) Math.log(2); /** * lookup table, source: Blast book, appendix C */ private static final String[] table = new String[] { "Matrix open extension lambda K H", "BLOSUM80 32767 32767 0.343 0.177 0.657", "BLOSUM80 25 2 0.342 0.170 0.660", "BLOSUM80 13 2 0.336 0.150 0.570", "BLOSUM80 9 2 0.319 0.110 0.420", "BLOSUM80 8 2 0.308 0.0900 0.350", "BLOSUM80 7 2 0.293 0.0700 0.270", "BLOSUM80 6 2 0.268 0.0450 0.190", "BLOSUM80 11 1 0.314 0.0950 0.350", "BLOSUM80 10 1 0.299 0.0710 0.270", "BLOSUM80 9 1 0.279 0.0480 0.200", "BLOSUM62 32767 32767 0.318 0.134 0.401", "BLOSUM62 11 2 0.297 0.0820 0.270", "BLOSUM62 10 2 0.291 0.0750 0.230", "BLOSUM62 9 2 0.279 0.0580 0.190", "BLOSUM62 8 2 0.264 0.0450 0.150", "BLOSUM62 7 2 0.239 0.0270 0.100", "BLOSUM62 6 2 0.201 0.0120 0.0610", "BLOSUM62 13 1 0.292 0.0710 0.230", "BLOSUM62 12 1 0.283 0.0590 0.190", "BLOSUM62 11 1 0.267 0.0410 0.140", "BLOSUM62 10 1 0.243 0.0240 0.100", "BLOSUM62 9 1 0.206 0.0100 0.0520", "BLOSUM50 32767 32767 0.232 0.112 0.336", "BLOSUM50 13 3 0.212 0.0630 0.190", "BLOSUM50 12 3 0.206 0.0550 0.170", "BLOSUM50 11 3 0.197 0.0420 0.140", "BLOSUM50 10 3 0.186 0.0310 0.110", "BLOSUM50 9 3 0.172 0.0220 0.0820", "BLOSUM50 16 2 0.215 0.0660 0.200", "BLOSUM50 15 2 0.210 0.0580 0.170", "BLOSUM50 14 2 0.202 0.0450 0.140", "BLOSUM50 13 2 0.193 0.0350 0.120", "BLOSUM50 12 2 0.181 0.0250 0.0950", "BLOSUM50 19 1 0.212 0.0570 0.180", "BLOSUM50 18 1 0.207 0.0500 0.150", "BLOSUM50 17 1 0.198 0.0370 0.120", "BLOSUM50 16 1 0.186 0.0250 0.100", "BLOSUM50 15 1 0.171 0.0150 0.0630", "BLOSUM45 32767 32767 0.229 0.0924 0.251", "BLOSUM45 13 3 0.207 0.0490 0.140", "BLOSUM45 12 3 0.199 0.0390 0.110", "BLOSUM45 11 3 0.190 0.0310 0.0950", "BLOSUM45 10 3 0.179 0.0230 0.0750", "BLOSUM45 16 2 0.210 0.0510 0.140", "BLOSUM45 15 2 0.203 0.0410 0.120", "BLOSUM45 14 2 0.195 0.0320 0.100", "BLOSUM45 13 2 0.185 0.0240 0.0840", "BLOSUM45 12 2 0.171 0.0160 0.0610", "BLOSUM45 19 1 0.205 0.0400 0.110", "BLOSUM45 18 1 0.198 0.0320 0.100", "BLOSUM45 17 1 0.189 0.0240 0.0790", "BLOSUM45 16 1 0.176 0.0160 0.0630", "BLOSUM90 32767 32767 0.335 0.190 0.755", "BLOSUM90 9 2 0.310 0.120 0.460", "BLOSUM90 8 2 0.300 0.0990 0.390", "BLOSUM90 7 2 0.283 0.0720 0.300", "BLOSUM90 6 2 0.259 0.0480 0.220", "BLOSUM90 11 1 0.302 0.0930 0.390", "BLOSUM90 10 1 0.290 0.0750 0.280", "BLOSUM90 9 1 0.265 0.0440 0.200" }; /** * constructor * * @param referenceLength * @param blosumName * @param gapOpenPenalty * @param gapExtensionPenalty */ public BlastStatisticsHelper(long referenceLength, String blosumName, int gapOpenPenalty, int gapExtensionPenalty) throws IOException { this.referenceLength = referenceLength; Pair pair = lookupLambdaAndK(blosumName, gapOpenPenalty, gapExtensionPenalty); this.lambda = pair.get1(); this.k = pair.get2(); this.lnK = (float) Math.log(k); System.err.println("Blast-stats: matrix=" + blosumName + " gapOpen=" + gapOpenPenalty + " gapExtend=" + gapExtensionPenalty + " lambda=" + getLambda() + " k=" + getK()); } /** * constructor * * @param referenceLength * @param k * @param lambda */ public BlastStatisticsHelper(long referenceLength, float k, float lambda) { this.referenceLength = referenceLength; this.k = k; this.lnK = (float) Math.log(k); this.lambda = lambda; } /** * set the reference length * * @param referenceLength */ public void setReferenceLength(long referenceLength) { this.referenceLength = referenceLength; } /** * get the bit score * * @param alignmentScore * @return bit score */ public double getBitScore(int alignmentScore) { return (lambda * alignmentScore - lnK) / LN2; } /** * get the e-value * * @param queryLength * @param alignmentScore * @return e-evalue */ public double getExpect(int queryLength, int alignmentScore) { return k * referenceLength * queryLength * Math.exp(-lambda * alignmentScore); } /** * get blast's k value * * @return k */ public double getK() { return k; } /** * get blast's lambda value * * @return lambda */ public double getLambda() { return lambda; } /** * lookup the blast K and Lambda values for a given setting * * @param blosumName * @param gapOpen * @param gapExtend * @return k and lambda * @throws IOException */ public static Pair lookupLambdaAndK(String blosumName, int gapOpen, int gapExtend) throws IOException { blosumName = blosumName.toUpperCase(); for (String line : table) { if (line.startsWith(blosumName)) { String[] tokens = line.split("\t"); if (tokens.length == 6) { int gop = Integer.parseInt(tokens[1]); int gep = Integer.parseInt(tokens[2]); if (gop == gapOpen && gep == gapExtend) { return new Pair<>(Double.parseDouble(tokens[3]), Double.parseDouble(tokens[4])); } } } } System.err.println("Known combinations of BLOSUM matrices and gap penalties:"); System.err.println(Basic.toString(table, "\n")); throw new IOException("Can't determine BLAST statistics for given combination of BLOSUM matrix and gap penalties"); } } malt-0.5.2/src/malt/align/DNAScoringMatrix.java000066400000000000000000000030261400455127600212550ustar00rootroot00000000000000/* * DNAScoringMatrix.java Copyright (C) 2020. Daniel H. Huson * * (Some files contain contributions from other authors, who are then mentioned separately.) * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * */ package malt.align; /** * Basic DNA scoring matrix * Daniel Huson, 8.2014 */ public class DNAScoringMatrix implements IScoringMatrix { private final int[][] matrix = new int[128][128]; public DNAScoringMatrix(int matchScore, int mismatchScore) { for (int i = 0; i < 128; i++) { matrix[i][i] = matchScore; for (int j = i + 1; j < 128; j++) matrix[i][j] = matrix[j][i] = mismatchScore; } } /** * get score for letters a and b * * @param a * @param b * @return score */ public int getScore(byte a, byte b) { return matrix[a][b]; } @Override public int[][] getMatrix() { return matrix; } } malt-0.5.2/src/malt/align/IScoringMatrix.java000066400000000000000000000022701400455127600210430ustar00rootroot00000000000000/* * IScoringMatrix.java Copyright (C) 2020. Daniel H. Huson * * (Some files contain contributions from other authors, who are then mentioned separately.) * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * */ package malt.align; /** * interface for scoring matrix * Daniel Huson, 8.2014 */ public interface IScoringMatrix { /** * gets the score for aligning letters a and b * * @param a * @param b * @return score */ int getScore(byte a, byte b); /** * get the scoring matrix * * @return matrix */ int[][] getMatrix(); } malt-0.5.2/src/malt/align/ProteinScoringMatrix.java000066400000000000000000000474501400455127600223040ustar00rootroot00000000000000/* * ProteinScoringMatrix.java Copyright (C) 2020. Daniel H. Huson * * (Some files contain contributions from other authors, who are then mentioned separately.) * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * */ package malt.align; import java.io.BufferedReader; import java.io.IOException; import java.io.Reader; import java.io.StringReader; import java.util.Arrays; /** * A number of different BLOSUM matrices * Daniel Huson, 11.2011 */ public class ProteinScoringMatrix implements IScoringMatrix { public enum ScoringScheme {BLOSUM45, BLOSUM50, BLOSUM62, BLOSUM80, BLOSUM90} private final int[][] matrix; private static ProteinScoringMatrix BLOSUM90; private static ProteinScoringMatrix BLOSUM80; private static ProteinScoringMatrix BLOSUM62; private static ProteinScoringMatrix BLOSUM50; private static ProteinScoringMatrix BLOSUM45; public final static String BLOSUM90_INPUT = "A R N D C Q E G H I L K M F P S T W Y V B J Z X * \n" + "A 5 -2 -2 -3 -1 -1 -1 0 -2 -2 -2 -1 -2 -3 -1 1 0 -4 -3 -1 -2 -2 -1 -1 -6 \n" + "R -2 6 -1 -3 -5 1 -1 -3 0 -4 -3 2 -2 -4 -3 -1 -2 -4 -3 -3 -2 -3 0 -1 -6 \n" + "N -2 -1 7 1 -4 0 -1 -1 0 -4 -4 0 -3 -4 -3 0 0 -5 -3 -4 5 -4 -1 -1 -6 \n" + "D -3 -3 1 7 -5 -1 1 -2 -2 -5 -5 -1 -4 -5 -3 -1 -2 -6 -4 -5 5 -5 1 -1 -6 \n" + "C -1 -5 -4 -5 9 -4 -6 -4 -5 -2 -2 -4 -2 -3 -4 -2 -2 -4 -4 -2 -4 -2 -5 -1 -6 \n" + "Q -1 1 0 -1 -4 7 2 -3 1 -4 -3 1 0 -4 -2 -1 -1 -3 -3 -3 -1 -3 5 -1 -6 \n" + "E -1 -1 -1 1 -6 2 6 -3 -1 -4 -4 0 -3 -5 -2 -1 -1 -5 -4 -3 1 -4 5 -1 -6 \n" + "G 0 -3 -1 -2 -4 -3 -3 6 -3 -5 -5 -2 -4 -5 -3 -1 -3 -4 -5 -5 -2 -5 -3 -1 -6 \n" + "H -2 0 0 -2 -5 1 -1 -3 8 -4 -4 -1 -3 -2 -3 -2 -2 -3 1 -4 -1 -4 0 -1 -6 \n" + "I -2 -4 -4 -5 -2 -4 -4 -5 -4 5 1 -4 1 -1 -4 -3 -1 -4 -2 3 -5 3 -4 -1 -6 \n" + "L -2 -3 -4 -5 -2 -3 -4 -5 -4 1 5 -3 2 0 -4 -3 -2 -3 -2 0 -5 4 -4 -1 -6 \n" + "K -1 2 0 -1 -4 1 0 -2 -1 -4 -3 6 -2 -4 -2 -1 -1 -5 -3 -3 -1 -3 1 -1 -6 \n" + "M -2 -2 -3 -4 -2 0 -3 -4 -3 1 2 -2 7 -1 -3 -2 -1 -2 -2 0 -4 2 -2 -1 -6 \n" + "F -3 -4 -4 -5 -3 -4 -5 -5 -2 -1 0 -4 -1 7 -4 -3 -3 0 3 -2 -4 0 -4 -1 -6 \n" + "P -1 -3 -3 -3 -4 -2 -2 -3 -3 -4 -4 -2 -3 -4 8 -2 -2 -5 -4 -3 -3 -4 -2 -1 -6 \n" + "S 1 -1 0 -1 -2 -1 -1 -1 -2 -3 -3 -1 -2 -3 -2 5 1 -4 -3 -2 0 -3 -1 -1 -6 \n" + "T 0 -2 0 -2 -2 -1 -1 -3 -2 -1 -2 -1 -1 -3 -2 1 6 -4 -2 -1 -1 -2 -1 -1 -6 \n" + "W -4 -4 -5 -6 -4 -3 -5 -4 -3 -4 -3 -5 -2 0 -5 -4 -4 11 2 -3 -6 -3 -4 -1 -6 \n" + "Y -3 -3 -3 -4 -4 -3 -4 -5 1 -2 -2 -3 -2 3 -4 -3 -2 2 8 -3 -4 -2 -3 -1 -6 \n" + "V -1 -3 -4 -5 -2 -3 -3 -5 -4 3 0 -3 0 -2 -3 -2 -1 -3 -3 5 -4 1 -3 -1 -6 \n" + "B -2 -2 5 5 -4 -1 1 -2 -1 -5 -5 -1 -4 -4 -3 0 -1 -6 -4 -4 5 -5 0 -1 -6 \n" + "J -2 -3 -4 -5 -2 -3 -4 -5 -4 3 4 -3 2 0 -4 -3 -2 -3 -2 1 -5 4 -4 -1 -6 \n" + "Z -1 0 -1 1 -5 5 5 -3 0 -4 -4 1 -2 -4 -2 -1 -1 -4 -3 -3 0 -4 5 -1 -6 \n" + "X -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -6 \n" + "* -6 -6 -6 -6 -6 -6 -6 -6 -6 -6 -6 -6 -6 -6 -6 -6 -6 -6 -6 -6 -6 -6 -6 -6 1 \n"; public final static String BLOSUM80_INPUT = "A R N D C Q E G H I L K M F P S T W Y V B J Z X * \n" + "A 5 -2 -2 -2 -1 -1 -1 0 -2 -2 -2 -1 -1 -3 -1 1 0 -3 -2 0 -2 -2 -1 -1 -6 \n" + "R -2 6 -1 -2 -4 1 -1 -3 0 -3 -3 2 -2 -4 -2 -1 -1 -4 -3 -3 -1 -3 0 -1 -6 \n" + "N -2 -1 6 1 -3 0 -1 -1 0 -4 -4 0 -3 -4 -3 0 0 -4 -3 -4 5 -4 0 -1 -6 \n" + "D -2 -2 1 6 -4 -1 1 -2 -2 -4 -5 -1 -4 -4 -2 -1 -1 -6 -4 -4 5 -5 1 -1 -6 \n" + "C -1 -4 -3 -4 9 -4 -5 -4 -4 -2 -2 -4 -2 -3 -4 -2 -1 -3 -3 -1 -4 -2 -4 -1 -6 \n" + "Q -1 1 0 -1 -4 6 2 -2 1 -3 -3 1 0 -4 -2 0 -1 -3 -2 -3 0 -3 4 -1 -6 \n" + "E -1 -1 -1 1 -5 2 6 -3 0 -4 -4 1 -2 -4 -2 0 -1 -4 -3 -3 1 -4 5 -1 -6 \n" + "G 0 -3 -1 -2 -4 -2 -3 6 -3 -5 -4 -2 -4 -4 -3 -1 -2 -4 -4 -4 -1 -5 -3 -1 -6 \n" + "H -2 0 0 -2 -4 1 0 -3 8 -4 -3 -1 -2 -2 -3 -1 -2 -3 2 -4 -1 -4 0 -1 -6 \n" + "I -2 -3 -4 -4 -2 -3 -4 -5 -4 5 1 -3 1 -1 -4 -3 -1 -3 -2 3 -4 3 -4 -1 -6 \n" + "L -2 -3 -4 -5 -2 -3 -4 -4 -3 1 4 -3 2 0 -3 -3 -2 -2 -2 1 -4 3 -3 -1 -6 \n" + "K -1 2 0 -1 -4 1 1 -2 -1 -3 -3 5 -2 -4 -1 -1 -1 -4 -3 -3 -1 -3 1 -1 -6 \n" + "M -1 -2 -3 -4 -2 0 -2 -4 -2 1 2 -2 6 0 -3 -2 -1 -2 -2 1 -3 2 -1 -1 -6 \n" + "F -3 -4 -4 -4 -3 -4 -4 -4 -2 -1 0 -4 0 6 -4 -3 -2 0 3 -1 -4 0 -4 -1 -6 \n" + "P -1 -2 -3 -2 -4 -2 -2 -3 -3 -4 -3 -1 -3 -4 8 -1 -2 -5 -4 -3 -2 -4 -2 -1 -6 \n" + "S 1 -1 0 -1 -2 0 0 -1 -1 -3 -3 -1 -2 -3 -1 5 1 -4 -2 -2 0 -3 0 -1 -6 \n" + "T 0 -1 0 -1 -1 -1 -1 -2 -2 -1 -2 -1 -1 -2 -2 1 5 -4 -2 0 -1 -1 -1 -1 -6 \n" + "W -3 -4 -4 -6 -3 -3 -4 -4 -3 -3 -2 -4 -2 0 -5 -4 -4 11 2 -3 -5 -3 -3 -1 -6 \n" + "Y -2 -3 -3 -4 -3 -2 -3 -4 2 -2 -2 -3 -2 3 -4 -2 -2 2 7 -2 -3 -2 -3 -1 -6 \n" + "V 0 -3 -4 -4 -1 -3 -3 -4 -4 3 1 -3 1 -1 -3 -2 0 -3 -2 4 -4 2 -3 -1 -6 \n" + "B -2 -1 5 5 -4 0 1 -1 -1 -4 -4 -1 -3 -4 -2 0 -1 -5 -3 -4 5 -4 0 -1 -6 \n" + "J -2 -3 -4 -5 -2 -3 -4 -5 -4 3 3 -3 2 0 -4 -3 -1 -3 -2 2 -4 3 -3 -1 -6 \n" + "Z -1 0 0 1 -4 4 5 -3 0 -4 -3 1 -1 -4 -2 0 -1 -3 -3 -3 0 -3 5 -1 -6 \n" + "X -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -6 \n" + "* -6 -6 -6 -6 -6 -6 -6 -6 -6 -6 -6 -6 -6 -6 -6 -6 -6 -6 -6 -6 -6 -6 -6 -6 1 \n"; public final static String BLOSUM62_INPUT = "A R N D C Q E G H I L K M F P S T W Y V B Z X *\n" + "A 4 -1 -2 -2 0 -1 -1 0 -2 -1 -1 -1 -1 -2 -1 1 0 -3 -2 0 -2 -1 0 -4 \n" + "R -1 5 0 -2 -3 1 0 -2 0 -3 -2 2 -1 -3 -2 -1 -1 -3 -2 -3 -1 0 -1 -4 \n" + "N -2 0 6 1 -3 0 0 0 1 -3 -3 0 -2 -3 -2 1 0 -4 -2 -3 3 0 -1 -4 \n" + "D -2 -2 1 6 -3 0 2 -1 -1 -3 -4 -1 -3 -3 -1 0 -1 -4 -3 -3 4 1 -1 -4 \n" + "C 0 -3 -3 -3 9 -3 -4 -3 -3 -1 -1 -3 -1 -2 -3 -1 -1 -2 -2 -1 -3 -3 -2 -4 \n" + "Q -1 1 0 0 -3 5 2 -2 0 -3 -2 1 0 -3 -1 0 -1 -2 -1 -2 0 3 -1 -4 \n" + "E -1 0 0 2 -4 2 5 -2 0 -3 -3 1 -2 -3 -1 0 -1 -3 -2 -2 1 4 -1 -4 \n" + "G 0 -2 0 -1 -3 -2 -2 6 -2 -4 -4 -2 -3 -3 -2 0 -2 -2 -3 -3 -1 -2 -1 -4 \n" + "H -2 0 1 -1 -3 0 0 -2 8 -3 -3 -1 -2 -1 -2 -1 -2 -2 2 -3 0 0 -1 -4 \n" + "I -1 -3 -3 -3 -1 -3 -3 -4 -3 4 2 -3 1 0 -3 -2 -1 -3 -1 3 -3 -3 -1 -4 \n" + "L -1 -2 -3 -4 -1 -2 -3 -4 -3 2 4 -2 2 0 -3 -2 -1 -2 -1 1 -4 -3 -1 -4 \n" + "K -1 2 0 -1 -3 1 1 -2 -1 -3 -2 5 -1 -3 -1 0 -1 -3 -2 -2 0 1 -1 -4 \n" + "M -1 -1 -2 -3 -1 0 -2 -3 -2 1 2 -1 5 0 -2 -1 -1 -1 -1 1 -3 -1 -1 -4 \n" + "F -2 -3 -3 -3 -2 -3 -3 -3 -1 0 0 -3 0 6 -4 -2 -2 1 3 -1 -3 -3 -1 -4 \n" + "P -1 -2 -2 -1 -3 -1 -1 -2 -2 -3 -3 -1 -2 -4 7 -1 -1 -4 -3 -2 -2 -1 -2 -4 \n" + "S 1 -1 1 0 -1 0 0 0 -1 -2 -2 0 -1 -2 -1 4 1 -3 -2 -2 0 0 0 -4 \n" + "T 0 -1 0 -1 -1 -1 -1 -2 -2 -1 -1 -1 -1 -2 -1 1 5 -2 -2 0 -1 -1 0 -4 \n" + "W -3 -3 -4 -4 -2 -2 -3 -2 -2 -3 -2 -3 -1 1 -4 -3 -2 11 2 -3 -4 -3 -2 -4 \n" + "Y -2 -2 -2 -3 -2 -1 -2 -3 2 -1 -1 -2 -1 3 -3 -2 -2 2 7 -1 -3 -2 -1 -4 \n" + "V 0 -3 -3 -3 -1 -2 -2 -3 -3 3 1 -2 1 -1 -2 -2 0 -3 -1 4 -3 -2 -1 -4 \n" + "B -2 -1 3 4 -3 0 1 -1 0 -3 -4 0 -3 -3 -2 0 -1 -4 -3 -3 4 1 -1 -4 \n" + "Z -1 0 0 1 -3 3 4 -2 0 -3 -3 1 -1 -3 -1 0 -1 -3 -2 -2 1 4 -1 -4 \n" + "X 0 -1 -1 -1 -2 -1 -1 -1 -1 -1 -1 -1 -1 -1 -2 0 0 -2 -1 -1 -1 -1 -1 -4 \n" + "* -4 -4 -4 -4 -4 -4 -4 -4 -4 -4 -4 -4 -4 -4 -4 -4 -4 -4 -4 -4 -4 -4 -4 1 \n"; public final static String BLOSUM50_INPUT = "A R N D C Q E G H I L K M F P S T W Y V B Z X * \n" + "A 5 -2 -1 -2 -1 -1 -1 0 -2 -1 -2 -1 -1 -3 -1 1 0 -3 -2 0 -2 -1 -1 -5 \n" + "R -2 7 -1 -2 -4 1 0 -3 0 -4 -3 3 -2 -3 -3 -1 -1 -3 -1 -3 -1 0 -1 -5 \n" + "N -1 -1 7 2 -2 0 0 0 1 -3 -4 0 -2 -4 -2 1 0 -4 -2 -3 4 0 -1 -5 \n" + "D -2 -2 2 8 -4 0 2 -1 -1 -4 -4 -1 -4 -5 -1 0 -1 -5 -3 -4 5 1 -1 -5 \n" + "C -1 -4 -2 -4 13 -3 -3 -3 -3 -2 -2 -3 -2 -2 -4 -1 -1 -5 -3 -1 -3 -3 -2 -5 \n" + "Q -1 1 0 0 -3 7 2 -2 1 -3 -2 2 0 -4 -1 0 -1 -1 -1 -3 0 4 -1 -5 \n" + "E -1 0 0 2 -3 2 6 -3 0 -4 -3 1 -2 -3 -1 -1 -1 -3 -2 -3 1 5 -1 -5 \n" + "G 0 -3 0 -1 -3 -2 -3 8 -2 -4 -4 -2 -3 -4 -2 0 -2 -3 -3 -4 -1 -2 -2 -5 \n" + "H -2 0 1 -1 -3 1 0 -2 10 -4 -3 0 -1 -1 -2 -1 -2 -3 2 -4 0 0 -1 -5 \n" + "I -1 -4 -3 -4 -2 -3 -4 -4 -4 5 2 -3 2 0 -3 -3 -1 -3 -1 4 -4 -3 -1 -5 \n" + "L -2 -3 -4 -4 -2 -2 -3 -4 -3 2 5 -3 3 1 -4 -3 -1 -2 -1 1 -4 -3 -1 -5 \n" + "K -1 3 0 -1 -3 2 1 -2 0 -3 -3 6 -2 -4 -1 0 -1 -3 -2 -3 0 1 -1 -5 \n" + "M -1 -2 -2 -4 -2 0 -2 -3 -1 2 3 -2 7 0 -3 -2 -1 -1 0 1 -3 -1 -1 -5 \n" + "F -3 -3 -4 -5 -2 -4 -3 -4 -1 0 1 -4 0 8 -4 -3 -2 1 4 -1 -4 -4 -2 -5 \n" + "P -1 -3 -2 -1 -4 -1 -1 -2 -2 -3 -4 -1 -3 -4 10 -1 -1 -4 -3 -3 -2 -1 -2 -5 \n" + "S 1 -1 1 0 -1 0 -1 0 -1 -3 -3 0 -2 -3 -1 5 2 -4 -2 -2 0 0 -1 -5 \n" + "T 0 -1 0 -1 -1 -1 -1 -2 -2 -1 -1 -1 -1 -2 -1 2 5 -3 -2 0 0 -1 0 -5 \n" + "W -3 -3 -4 -5 -5 -1 -3 -3 -3 -3 -2 -3 -1 1 -4 -4 -3 15 2 -3 -5 -2 -3 -5 \n" + "Y -2 -1 -2 -3 -3 -1 -2 -3 2 -1 -1 -2 0 4 -3 -2 -2 2 8 -1 -3 -2 -1 -5 \n" + "V 0 -3 -3 -4 -1 -3 -3 -4 -4 4 1 -3 1 -1 -3 -2 0 -3 -1 5 -4 -3 -1 -5 \n" + "B -2 -1 4 5 -3 0 1 -1 0 -4 -4 0 -3 -4 -2 0 0 -5 -3 -4 5 2 -1 -5 \n" + "Z -1 0 0 1 -3 4 5 -2 0 -3 -3 1 -1 -4 -1 0 -1 -2 -2 -3 2 5 -1 -5 \n" + "X -1 -1 -1 -1 -2 -1 -1 -2 -1 -1 -1 -1 -1 -2 -2 -1 0 -3 -1 -1 -1 -1 -1 -5 \n" + "* -5 -5 -5 -5 -5 -5 -5 -5 -5 -5 -5 -5 -5 -5 -5 -5 -5 -5 -5 -5 -5 -5 -5 1 \n"; public final static String BLOSUM45_INPUT = "A R N D C Q E G H I L K M F P S T W Y V B J Z X * \n" + "A 5 -2 -1 -2 -1 -1 -1 0 -2 -1 -1 -1 -1 -2 -1 1 0 -2 -2 0 -1 -1 -1 -1 -5 \n" + "R -2 7 0 -1 -3 1 0 -2 0 -3 -2 3 -1 -2 -2 -1 -1 -2 -1 -2 -1 -3 1 -1 -5 \n" + "N -1 0 6 2 -2 0 0 0 1 -2 -3 0 -2 -2 -2 1 0 -4 -2 -3 5 -3 0 -1 -5 \n" + "D -2 -1 2 7 -3 0 2 -1 0 -4 -3 0 -3 -4 -1 0 -1 -4 -2 -3 6 -3 1 -1 -5 \n" + "C -1 -3 -2 -3 12 -3 -3 -3 -3 -3 -2 -3 -2 -2 -4 -1 -1 -5 -3 -1 -2 -2 -3 -1 -5 \n" + "Q -1 1 0 0 -3 6 2 -2 1 -2 -2 1 0 -4 -1 0 -1 -2 -1 -3 0 -2 4 -1 -5 \n" + "E -1 0 0 2 -3 2 6 -2 0 -3 -2 1 -2 -3 0 0 -1 -3 -2 -3 1 -3 5 -1 -5 \n" + "G 0 -2 0 -1 -3 -2 -2 7 -2 -4 -3 -2 -2 -3 -2 0 -2 -2 -3 -3 -1 -4 -2 -1 -5 \n" + "H -2 0 1 0 -3 1 0 -2 10 -3 -2 -1 0 -2 -2 -1 -2 -3 2 -3 0 -2 0 -1 -5 \n" + "I -1 -3 -2 -4 -3 -2 -3 -4 -3 5 2 -3 2 0 -2 -2 -1 -2 0 3 -3 4 -3 -1 -5 \n" + "L -1 -2 -3 -3 -2 -2 -2 -3 -2 2 5 -3 2 1 -3 -3 -1 -2 0 1 -3 4 -2 -1 -5 \n" + "K -1 3 0 0 -3 1 1 -2 -1 -3 -3 5 -1 -3 -1 -1 -1 -2 -1 -2 0 -3 1 -1 -5 \n" + "M -1 -1 -2 -3 -2 0 -2 -2 0 2 2 -1 6 0 -2 -2 -1 -2 0 1 -2 2 -1 -1 -5 \n" + "F -2 -2 -2 -4 -2 -4 -3 -3 -2 0 1 -3 0 8 -3 -2 -1 1 3 0 -3 1 -3 -1 -5 \n" + "P -1 -2 -2 -1 -4 -1 0 -2 -2 -2 -3 -1 -2 -3 9 -1 -1 -3 -3 -3 -2 -3 -1 -1 -5 \n" + "S 1 -1 1 0 -1 0 0 0 -1 -2 -3 -1 -2 -2 -1 4 2 -4 -2 -1 0 -2 0 -1 -5 \n" + "T 0 -1 0 -1 -1 -1 -1 -2 -2 -1 -1 -1 -1 -1 -1 2 5 -3 -1 0 0 -1 -1 -1 -5 \n" + "W -2 -2 -4 -4 -5 -2 -3 -2 -3 -2 -2 -2 -2 1 -3 -4 -3 15 3 -3 -4 -2 -2 -1 -5 \n" + "Y -2 -1 -2 -2 -3 -1 -2 -3 2 0 0 -1 0 3 -3 -2 -1 3 8 -1 -2 0 -2 -1 -5 \n" + "V 0 -2 -3 -3 -1 -3 -3 -3 -3 3 1 -2 1 0 -3 -1 0 -3 -1 5 -3 2 -3 -1 -5 \n" + "B -1 -1 5 6 -2 0 1 -1 0 -3 -3 0 -2 -3 -2 0 0 -4 -2 -3 5 -3 1 -1 -5 \n" + "J -1 -3 -3 -3 -2 -2 -3 -4 -2 4 4 -3 2 1 -3 -2 -1 -2 0 2 -3 4 -2 -1 -5 \n" + "Z -1 1 0 1 -3 4 5 -2 0 -3 -2 1 -1 -3 -1 0 -1 -2 -2 -3 1 -2 5 -1 -5 \n" + "X -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -5 \n" + "* -5 -5 -5 -5 -5 -5 -5 -5 -5 -5 -5 -5 -5 -5 -5 -5 -5 -5 -5 -5 -5 -5 -5 -5 1 \n"; /** * constructor */ public ProteinScoringMatrix() { matrix = new int[128][128]; for (int[] row : matrix) { Arrays.fill(row, 0, row.length, -20); } } /** * get the score for two letters * * @param a * @param b * @return score, or -20, if matrix not defined for (a,b) */ public int getScore(byte a, byte b) { return matrix[a][b]; } /** * creates a scoring matrix * * @param name * @return protein scoring matrix * @throws IOException */ public static ProteinScoringMatrix create(String name) throws IOException { return create(ScoringScheme.valueOf(name)); } /** * creates a scoring matrix * * @param which * @return protein scoring matrix * @throws IOException */ public static ProteinScoringMatrix create(ScoringScheme which) throws IOException { switch (which) { case BLOSUM90: return getBlosum90(); case BLOSUM80: return getBlosum80(); case BLOSUM62: return getBlosum62(); case BLOSUM50: return getBlosum50(); case BLOSUM45: return getBlosum45(); default: throw new IOException("Unrecognized BLOSUM matrix: " + which); } } /** * get the blosum 90 matrix * * @return blosum 90 */ public static ProteinScoringMatrix getBlosum90() { try { if (BLOSUM90 == null) { BLOSUM90 = new ProteinScoringMatrix(); BLOSUM90.load(new StringReader(BLOSUM90_INPUT)); } return BLOSUM90; } catch (Exception ex) { throw new RuntimeException(ex); } } /** * get the blosum 80 matrix * * @return blosum 80 */ public static ProteinScoringMatrix getBlosum80() { try { if (BLOSUM80 == null) { BLOSUM80 = new ProteinScoringMatrix(); BLOSUM80.load(new StringReader(BLOSUM80_INPUT)); } return BLOSUM80; } catch (Exception ex) { throw new RuntimeException(ex); } } /** * get the blosum 62 matrix * * @return blosum 62 */ public static ProteinScoringMatrix getBlosum62() { try { if (BLOSUM62 == null) { BLOSUM62 = new ProteinScoringMatrix(); BLOSUM62.load(new StringReader(BLOSUM62_INPUT)); } return BLOSUM62; } catch (Exception ex) { throw new RuntimeException(ex); } } /** * get the blosum 50 matrix * * @return blosum 50 */ public static ProteinScoringMatrix getBlosum50() { try { if (BLOSUM50 == null) { BLOSUM50 = new ProteinScoringMatrix(); BLOSUM50.load(new StringReader(BLOSUM50_INPUT)); } return BLOSUM50; } catch (Exception ex) { throw new RuntimeException(ex); } } /** * get the blosum 45 matrix * * @return blosum 45 */ public static ProteinScoringMatrix getBlosum45() { try { if (BLOSUM45 == null) { BLOSUM45 = new ProteinScoringMatrix(); BLOSUM45.load(new StringReader(BLOSUM45_INPUT)); } return BLOSUM45; } catch (Exception ex) { throw new RuntimeException(ex); } } /** * load a matrix * * @param r0 * @throws java.io.IOException */ public void load(Reader r0) throws IOException { BufferedReader r = new BufferedReader(r0); char[] mapPos2Char = null; String aLine; int cols = 0; while ((aLine = r.readLine()) != null) { aLine = aLine.trim(); if (aLine.length() == 0 || aLine.startsWith("#")) continue; if (mapPos2Char == null) // must be first line listing all letters { String[] tokens = aLine.split(" "); cols = tokens.length; if (tokens.length < 20) throw new IOException("Expected >=20 tokens, got: " + tokens.length + " in line: " + aLine); int count = 0; mapPos2Char = new char[tokens.length]; for (String label : tokens) { char c = Character.toUpperCase(label.charAt(0)); mapPos2Char[count++] = c; } } else // a definition line { String[] tokens = aLine.split(" "); if (tokens.length != cols + 1) throw new IOException("Expected " + (cols + 1) + " tokens, got: " + tokens.length + " in line: " + aLine); char c = Character.toUpperCase(tokens[0].charAt(0)); for (int i = 1; i < tokens.length; i++) { int value = Integer.parseInt(tokens[i]); char d = mapPos2Char[i - 1]; matrix[c][d] = value; matrix[c][Character.toLowerCase(d)] = value; matrix[Character.toLowerCase(c)][d] = value; matrix[Character.toLowerCase(c)][Character.toLowerCase(d)] = value; } } } } @Override public int[][] getMatrix() { return matrix; } } malt-0.5.2/src/malt/align/SimpleAligner4DNA.java000066400000000000000000000176541400455127600213170ustar00rootroot00000000000000/* * SimpleAligner4DNA.java Copyright (C) 2020. Daniel H. Huson * * (Some files contain contributions from other authors, who are then mentioned separately.) * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * */ package malt.align; import jloda.util.Basic; import jloda.util.BlastMode; import jloda.util.BoyerMoore; import jloda.util.Single; import java.util.Iterator; /** * convenience class for aligning a DNA query into a DNA reference * Created by huson on 2/9/16. */ public class SimpleAligner4DNA { public enum OverlapType {QuerySuffix2RefPrefix, QueryContainedInRef, QueryPrefix2RefSuffix, None} // what is query? private final AlignerOptions alignerOptions; private final BandedAligner bandedAligner; private int minRawScore = 1; private float minPercentIdentity = 0; public SimpleAligner4DNA() { alignerOptions = new AlignerOptions(); alignerOptions.setAlignmentType(AlignerOptions.AlignmentMode.SemiGlobal); alignerOptions.setScoringMatrix(new DNAScoringMatrix(alignerOptions.getMatchScore(), alignerOptions.getMismatchScore())); bandedAligner = new BandedAligner(alignerOptions, BlastMode.BlastN); } /** * compute a semi-global alignment between the query and the reference * * @param query * @param reference * @param queryPos * @param refPos * @param seedLength * @return true, if alignment found */ public boolean computeAlignment(byte[] query, byte[] reference, int queryPos, int refPos, int seedLength) { bandedAligner.computeAlignment(query, query.length, reference, reference.length, queryPos, refPos, seedLength); return bandedAligner.getRawScore() >= minRawScore && (minPercentIdentity == 0 || bandedAligner.getPercentIdentity() >= minPercentIdentity); } /** * set the parameters * * @param matchScore * @param mismatchScore * @param gapOpenPenality * @param gapExtensionPenality */ public void setAlignmentParameters(int matchScore, int mismatchScore, int gapOpenPenality, int gapExtensionPenality) { alignerOptions.setScoringMatrix(new DNAScoringMatrix(matchScore, mismatchScore)); alignerOptions.setGapOpenPenalty(gapOpenPenality); alignerOptions.setGapExtensionPenalty(gapExtensionPenality); } /** * get the min score to be attained * * @return */ public int getMinRawScore() { return minRawScore; } /** * set the min raw score * * @param minRawScore */ public void setMinRawScore(int minRawScore) { this.minRawScore = minRawScore; } /** * get the min percent identity * * @return */ public float getMinPercentIdentity() { return minPercentIdentity; } /** * set the min identity * * @param minPercentIdentity */ public void setMinPercentIdentity(float minPercentIdentity) { this.minPercentIdentity = minPercentIdentity; } /** * get simple alignment text * * @return as string */ public String getAlignmentString() { return Basic.toString(bandedAligner.getAlignmentSimpleText()); } /** * gets a position of the query in the reference, or reference.length if not contained * * @param query * @param reference * @param queryMustBeContained * @return pos or reference.length */ public int getPositionInReference(byte[] query, byte[] reference, boolean queryMustBeContained) { if (queryMustBeContained && getMinPercentIdentity() >= 100) { return (new BoyerMoore(query, 0, query.length, 127)).search(reference); } int bestQueryPos = 0; int bestRefPos = 0; int bestScore = 0; final int k = Math.max(10, (int) (100.0 / (100.0 - minPercentIdentity + 1))); // determine smallest exact match that must be present for (int queryPos = 0; queryPos < query.length - k + 1; queryPos += k) { BoyerMoore boyerMoore = new BoyerMoore(query, queryPos, k, 127); for (Iterator it = boyerMoore.iterator(reference); it.hasNext(); ) { int refPos = it.next(); if ((!queryMustBeContained && computeAlignment(query, reference, queryPos, refPos, k)) || (queryMustBeContained && refPos <= reference.length - query.length && computeAlignment(query, reference, queryPos, refPos, k) && bandedAligner.getAlignmentLength() >= query.length)) { { if (bandedAligner.getRawScore() > bestScore) { bestScore = bandedAligner.getRawScore(); bestQueryPos = queryPos; bestRefPos = refPos; } } } } } if (bestScore > 0) { computeAlignment(query, reference, bestQueryPos, bestRefPos, k); return bestRefPos; } return reference.length; } /** * gets the overlap type of the query in the reference * * @param query * @param reference * @param overlap length * @return type */ public OverlapType getOverlap(byte[] query, byte[] reference, Single overlap) { if (getPositionInReference(query, reference, false) != reference.length) { if (bandedAligner.getStartQuery() > 0 && bandedAligner.getStartReference() == 0 && bandedAligner.getAlignmentLength() < reference.length) { overlap.set(query.length - bandedAligner.getStartQuery()); return OverlapType.QuerySuffix2RefPrefix; } else if (bandedAligner.getStartQuery() == 0 && bandedAligner.getStartReference() > 0 && bandedAligner.getAlignmentLength() < query.length) { overlap.set(bandedAligner.getEndQuery()); return OverlapType.QueryPrefix2RefSuffix; } else if (bandedAligner.getStartQuery() == 0 && bandedAligner.getEndQuery() == query.length) { overlap.set(query.length); return OverlapType.QueryContainedInRef; } } overlap.set(0); return OverlapType.None; } /** * get the percent identity of the last alignment * * @return percent identity */ public float getPercentIdentity() { return bandedAligner.getPercentIdentity(); } /** * test this class * * @param args */ public static void main(String[] args) { final SimpleAligner4DNA simpleAligner4DNA = new SimpleAligner4DNA(); byte[] reference = "acttgcatcacgactacactgacacggctctttacatcggtatatcgctacacagtcacagactacacgtcacagcattt".getBytes(); //byte[] query="gactgtgtagcgatattaccgatgtaaagagcc".getBytes(); String[] queries = {"ggtatatcgctacacagtcacagactacacgtcacagcataaaaaaaa", "aaaaaaaaaaacttgcatcacgactacactgacacggctctttacatc" , "tatatcgctacacagtcacagactacacgtcacagc" }; simpleAligner4DNA.setMinPercentIdentity(90); for (String query : queries) { final Single overlap = new Single<>(0); System.err.println("Overlap type: " + simpleAligner4DNA.getOverlap(query.getBytes(), reference, overlap) + ", length=" + overlap); System.err.println(simpleAligner4DNA.getAlignmentString()); } } } malt-0.5.2/src/malt/data/000077500000000000000000000000001400455127600151145ustar00rootroot00000000000000malt-0.5.2/src/malt/data/BuildRow.java000066400000000000000000000060361400455127600175130ustar00rootroot00000000000000/* * BuildRow.java Copyright (C) 2020. Daniel H. Huson * * (Some files contain contributions from other authors, who are then mentioned separately.) * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * */ package malt.data; /** * a row of numbers that are stored in a larger array. * Daniel Huson, 8.2014 */ public class BuildRow { private int size; private int offset; private int[] containingArray; private final int[] pair = new int[]{0, 0}; /** * constructor */ public BuildRow() { } /** * Set the row. array[offset] must contain size, i.e. the number of integers to be used * array[offset+1]... array[offset+size-1] are the numbers * * @param array array containing size followed by entries * @param offset location of of size entry in array */ public void set(int[] array, int offset) { this.size = array[offset]; this.containingArray = array; this.offset = offset + 1; } /** * setting a single pair of numbers * * @param firstNumber * @param secondNumber */ public void setPair(int firstNumber, int secondNumber) { size = 2; offset = 0; pair[0] = firstNumber; pair[1] = secondNumber; containingArray = pair; } /** * set to empty */ public void setEmpty() { size = 0; } /** * gets the number of int in this row * * @return size */ public int size() { return size; } /** * use this to access numbers 0,..,size-1 * * @param index * @return item */ public int get(int index) { return containingArray[offset + index]; } /** * get array that contains numbers * * @return full row */ public int[] getContainingArray() { return containingArray; } /** * get offset at which numbers start (position of size entry plus 1) * * @return offset */ public int getOffset() { return offset; } /** * get string representation * * @return */ public String toString() { if (size > 0) { StringBuilder buf = new StringBuilder(); buf.append("(").append(size()).append("): "); for (int i = 0; i < size(); i++) buf.append(" ").append(get(i)); return buf.toString(); } else return "null"; } } malt-0.5.2/src/malt/data/DNA5.java000066400000000000000000000154021400455127600164500ustar00rootroot00000000000000/* * DNA5.java Copyright (C) 2020. Daniel H. Huson * * (Some files contain contributions from other authors, who are then mentioned separately.) * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * */ package malt.data; /** * DNA characters * Daniel Huson, 8.2014 */ public class DNA5 implements IAlphabet { final static private byte[] normalizedLetters = { 'N', 'N', 'N', 'N', 'N', 'N', 'N', 'N', 'N', 'N', 'N', 'N', 'N', 'N', 'N', 'N', 'N', 'N', 'N', 'N', 'N', 'N', 'N', 'N', 'N', 'N', 'N', 'N', 'N', 'N', 'N', 'N', 'N', 'N', 'N', 'N', 'N', 'N', 'N', 'N', 'N', 'N', 'N', 'N', 'N', '-', 'N', 'N', 'N', 'N', 'N', 'N', 'N', 'N', 'N', 'N', 'N', 'N', 'N', 'N', 'N', 'N', 'N', 'N', 'N', 'A', 'N', 'C', 'N', 'N', 'N', 'G', 'N', 'N', 'N', 'N', 'N', 'N', 'N', 'N', 'N', 'N', 'N', 'N', 'T', 'T', 'N', 'N', 'N', 'N', 'N', 'N', 'N', 'N', 'N', 'N', 'N', 'A', 'N', 'C', 'N', 'N', 'N', 'G', 'N', 'N', 'N', 'N', 'N', 'N', 'N', 'N', 'N', 'N', 'N', 'N', 'T', 'T', 'N', 'N', 'N', 'N', 'N', 'N', 'N', 'N', 'N' }; final static private byte[] normalizedComplement = { 'N', 'N', 'N', 'N', 'N', 'N', 'N', 'N', 'N', 'N', 'N', 'N', 'N', 'N', 'N', 'N', 'N', 'N', 'N', 'N', 'N', 'N', 'N', 'N', 'N', 'N', 'N', 'N', 'N', 'N', 'N', 'N', 'N', 'N', 'N', 'N', 'N', 'N', 'N', 'N', 'N', 'N', 'N', 'N', 'N', '-', 'N', 'N', 'N', 'N', 'N', 'N', 'N', 'N', 'N', 'N', 'N', 'N', 'N', 'N', 'N', 'N', 'N', 'N', 'N', 'T', 'N', 'G', 'N', 'N', 'N', 'C', 'N', 'N', 'N', 'N', 'N', 'N', 'N', 'N', 'N', 'N', 'N', 'N', 'A', 'A', 'N', 'N', 'N', 'N', 'N', 'N', 'N', 'N', 'N', 'N', 'N', 'T', 'N', 'G', 'N', 'N', 'N', 'C', 'N', 'N', 'N', 'N', 'N', 'N', 'N', 'N', 'N', 'N', 'N', 'N', 'A', 'A', 'N', 'N', 'N', 'N', 'N', 'N', 'N', 'N', 'N' }; private static final DNA5 instance = new DNA5(); /** * return an instance * * @return instance */ public static DNA5 getInstance() { return instance; } /** * maps letter to 'A', 'C', 'G', 'T' or 'N' * * @param letter * @return */ public byte getNormalized(byte letter) { return DNA5.normalizedLetters[letter]; } /** * normalize a sequence * * @param sequence */ public void normalize(byte[] sequence) { for (int i = 0; i < sequence.length; i++) { sequence[i] = getNormalized(sequence[i]); } } /** * get complement of base * * @param letter * @return */ public byte getBaseComplement(byte letter) { return normalizedComplement[letter]; } /** * do letters a and b correspond to the same base? * * @param a * @param b * @return true, if equalOverShorterOfBoth bases */ public boolean equal(byte a, byte b) { return normalizedLetters[a] == normalizedLetters[b]; } /** * do strings a and b correspond to the same DNA sequences? * * @param a * @param b * @return true, if equalOverShorterOfBoth DNA sequences */ public boolean equal(byte[] a, byte[] b) { if (a.length != b.length) return false; for (int i = 0; i < a.length; i++) if (normalizedLetters[a[i]] != normalizedLetters[b[i]]) return false; return true; } /** * gets reverse complement of a DNA sequence * * @param sequence * @return reverse complement */ public byte[] getReverseComplement(byte[] sequence) { byte[] result = new byte[sequence.length]; for (int i = 0; i < sequence.length; i++) { result[i] = normalizedComplement[sequence[sequence.length - 1 - i]]; } return result; } /** * gets reverse complement of a DNA sequence * * @param sequence * @param length * @param reverseComplement */ public void getReverseComplement(byte[] sequence, int length, byte[] reverseComplement) { for (int i = 0; i < length; i++) { reverseComplement[i] = normalizedComplement[sequence[length - 1 - i]]; } } /** * gets reverse, but not complement, of a DNA sequence * * @param sequence * @param length * @param reverse */ public void getReverseNotComplement(byte[] sequence, int length, byte[] reverse) { for (int i = 0; i < length; i++) { reverse[i] = sequence[length - 1 - i]; } } /** * is this a protein alphabet? * * @return true, if protein */ public boolean isProtein() { return false; } /** * is this a DNA alphabet? * * @return true, if DNA */ public boolean isDNA() { return true; } @Override public String getName() { return "DNA"; } /** * reverse complement in place * * @param bytes */ public void reverseComplement(byte[] bytes) { int top = (bytes.length + 1) / 2; for (int i = 0; i < top; i++) { int j = bytes.length - (i + 1); byte tmp = bytes[i]; bytes[i] = getBaseComplement(bytes[j]); bytes[j] = getBaseComplement(tmp); } } /** * reverse (but no complement) in place * * @param bytes */ public void reverse(byte[] bytes) { int top = bytes.length / 2; for (int i = 0; i < top; i++) { int j = bytes.length - (i + 1); byte tmp = bytes[i]; bytes[i] = bytes[j]; bytes[j] = tmp; } } /** * size * * @return size */ public int size() { return 5; } /** * a DNA seed is good it does not contain an N and contains at least two different letters * * @param word * @param length * @return */ @Override public boolean isGoodSeed(byte[] word, int length) { byte a = word[0]; byte b = 0; for (int i = 0; i < length; i++) { if (word[i] == 'N') return false; else if (b == 0 && word[i] != a) b = word[i]; } return b != 0; } } malt-0.5.2/src/malt/data/IAlphabet.java000066400000000000000000000035711400455127600176160ustar00rootroot00000000000000/* * IAlphabet.java Copyright (C) 2020. Daniel H. Huson * * (Some files contain contributions from other authors, who are then mentioned separately.) * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * */ package malt.data; /** * DNA or protein alphabet * Daniel Huson, 8.2014 */ public interface IAlphabet extends INormalizer { /** * maps letter to normalized base or amino acid * * @param letter * @return */ byte getNormalized(byte letter); /** * do letters a and b correspond to the same base or amino acid? * * @param a * @param b * @return true, if equalOverShorterOfBoth bases */ boolean equal(byte a, byte b); /** * is this a protein alphabet? * * @return true, if protein */ boolean isProtein(); /** * is this a DNA alphabet? * * @return true, if DNA */ boolean isDNA(); /** * gets the name of this alphabet * * @return name */ String getName(); /** * get the number of different letters * * @return size */ int size(); /** * is this word a good seed? * * @param word * @param length * @return true, if good */ boolean isGoodSeed(byte[] word, int length); } malt-0.5.2/src/malt/data/INormalizer.java000066400000000000000000000020721400455127600202130ustar00rootroot00000000000000/* * INormalizer.java Copyright (C) 2020. Daniel H. Huson * * (Some files contain contributions from other authors, who are then mentioned separately.) * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * */ package malt.data; /** * normalization interface * Daniel Huson, 10.2014 */ public interface INormalizer { /** * returns normalized letter * * @param letter * @return normalized letter */ byte getNormalized(byte letter); } malt-0.5.2/src/malt/data/ISequenceAccessor.java000066400000000000000000000021071400455127600213230ustar00rootroot00000000000000/* * ISequenceAccessor.java Copyright (C) 2020. Daniel H. Huson * * (Some files contain contributions from other authors, who are then mentioned separately.) * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * */ package malt.data; /** * Seed accessor * Daniel Huson, 2014 */ public interface ISequenceAccessor { int getNumberOfSequences(); byte[] getHeader(int index); byte[] getSequence(int index); void extendHeader(int index, String tag, Integer id); } malt-0.5.2/src/malt/data/ProteinAlphabet.java000066400000000000000000000077271400455127600210550ustar00rootroot00000000000000/* * ProteinAlphabet.java Copyright (C) 2020. Daniel H. Huson * * (Some files contain contributions from other authors, who are then mentioned separately.) * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * */ package malt.data; /** * implements a protein alphabet * Daniel Huson, 8.2014 */ public class ProteinAlphabet implements IAlphabet { final static private byte[] normalizedLetters = { 'X', 'X', 'X', 'X', 'X', 'X', 'X', 'X', 'X', 'X', 'X', 'X', 'X', 'X', 'X', 'X', 'X', 'X', 'X', 'X', 'X', 'X', 'X', 'X', 'X', 'X', 'X', 'X', 'X', 'X', 'X', 'X', 'X', 'X', 'X', 'X', 'X', 'X', 'X', 'X', 'X', 'X', '*', 'X', 'X', 'X', 'X', 'X', 'X', 'X', 'X', 'X', 'X', 'X', 'X', 'X', 'X', 'X', 'X', 'X', 'X', 'X', 'X', 'X', 'X', 'A', 'X', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'X', 'K', 'L', 'M', 'N', 'X', 'P', 'Q', 'R', 'S', 'T', 'X', 'V', 'W', 'X', 'Y', 'X', 'X', 'X', 'X', 'X', 'X', 'X', 'A', 'X', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'X', 'K', 'L', 'M', 'N', 'X', 'P', 'Q', 'R', 'S', 'T', 'X', 'V', 'W', 'X', 'Y', 'X', 'X', 'X', 'X', 'X', 'X'}; private static final ProteinAlphabet instance = new ProteinAlphabet(); /** * return an instance * * @return instance */ public static ProteinAlphabet getInstance() { return instance; } /** * maps letter to normalized base or amino acid * * @param letter * @return */ public byte getNormalized(byte letter) { return normalizedLetters[letter]; } /** * do letters a and b correspond to the same base or amino acid? * * @param a * @param b * @return true, if equalOverShorterOfBoth bases */ public boolean equal(byte a, byte b) { return normalizedLetters[a] == normalizedLetters[b]; } @Override public String getName() { return "PROTEIN"; } /** * returns the used alphabet * * @return alphabet */ public String toString() { return "A C D E F G H I K [L*] M N P Q R S T V W X Y"; } /** * is this a protein alphabet? * * @return true, if protein */ public boolean isProtein() { return true; } /** * is this a DNA alphabet? * * @return true, if DNA */ public boolean isDNA() { return false; } public int size() { return 20; } /** * a protein seed is a good seed if it contains more than 2 different letters and no unknown * * @param word * @param length * @return */ @Override public boolean isGoodSeed(byte[] word, int length) { final byte a = word[0]; byte b = 0; byte c = 0; for (int i = 0; i < length; i++) { final byte z = word[i]; if (z == 'X') return false; if (z != a) { if (b == 0) b = z; else if (c == 0 && z != b) c = z; } } return b != 0 && c != 0; } public static void main(String[] args) { for (int i = 0; i < 128; i++) { char ch = Character.toUpperCase((char) i); if ("ACDEFGHIKLMNPQRSTVWXY*".contains("" + ch)) System.err.print(" '" + ch + "',"); else System.err.print(" 'X',"); } } } malt-0.5.2/src/malt/data/QuerySequence2MatchesCache.java000066400000000000000000000207671400455127600231040ustar00rootroot00000000000000/* * QuerySequence2MatchesCache.java Copyright (C) 2020. Daniel H. Huson * * (Some files contain contributions from other authors, who are then mentioned separately.) * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * */ package malt.data; import jloda.thirdparty.MurmurHash3; /** * hash table used for caching matches associated with a given read * Created by huson on 7/9/14. */ public class QuerySequence2MatchesCache { private final int hashMask; private final Item[] hash2data; private final int randomNumberSeed = 666; private final int numberOfSyncObjects = (1 << 10); private final int syncObjectsMask = numberOfSyncObjects - 1; // use lots of objects to synchronize on so that threads don't in each others way private final Object[] syncTable = new Object[numberOfSyncObjects]; private long countGet = 0; private long countPut = 0; /** * constructor * * @param bits * @throws Exception */ public QuerySequence2MatchesCache(int bits) { if (bits > 31) throw new RuntimeException("bits exceed 31"); hash2data = new Item[1 << bits]; hashMask = (1 << bits) - 1; for (int i = 0; i < numberOfSyncObjects; i++) { syncTable[i] = new Object(); } } /** * put a copy into the cache * * @param sequence * @param sequenceLength * @param matches * @param numberOfMatches */ public void put(byte[] sequence, int sequenceLength, ReadMatch[] matches, int numberOfMatches) { int hash = getHash(sequence, sequenceLength); synchronized (syncTable[hash & syncObjectsMask]) { if (hash2data[hash] == null) // check again here, because could have been set while waiting... { // System.err.println("Put: "+ Basic.toString(sequence, sequenceLength)+" hash: "+hash); Item item = hash2data[hash]; if (item == null) { hash2data[hash] = new Item(sequence, sequenceLength, matches, numberOfMatches); countPut++; } else if (item.addIfNew(sequence, sequenceLength, matches, numberOfMatches)) countPut++; } } } /** * get the read matches associated with this sequence, if cached * * @param sequence * @param sequenceLength * @return associated read matches */ public ReadMatch[] get(byte[] sequence, int sequenceLength) { int hash = getHash(sequence, sequenceLength); // System.err.println("Get: "+ Basic.toString(sequence, sequenceLength)+" hash: "+hash); synchronized (syncTable[hash & syncObjectsMask]) { Item item = hash2data[hash]; if (item != null) { countGet++; return item.getMatches(sequence, sequenceLength); // get matches if correct sequence found } return null; } } /** * for a given key, add the reference id and sequence offset to table * uses very naive synchronization * * @param key * @return hash value */ private int getHash(byte[] key, int length) { int value = MurmurHash3.murmurhash3x8632(key, 0, length, randomNumberSeed) & hashMask; // & also removes negative sign if (value >= hash2data.length) value %= hash2data.length; return value; } /** * report stats on usage of the table */ public void reportStats() { System.err.println("Replicate query cache: in=" + countPut + ", out=" + countGet); } /** * determine whether cache contains this sequence * * @param sequence * @param sequenceLength * @return true, if sequence contained in cache */ public boolean contains(byte[] sequence, int sequenceLength) { int hash = getHash(sequence, sequenceLength); // System.err.println("Get: "+ Basic.toString(sequence, sequenceLength)+" hash: "+hash); synchronized (syncTable[hash & syncObjectsMask]) { Item item = hash2data[hash]; return item != null && item.contains(sequence, sequenceLength); } } /** * hash table item */ class Item { private Item next; private final byte[] sequence; private final ReadMatch[] matches; /** * constructor * * @param sequence * @param sequenceLength * @param matches * @param numberOfMatches */ public Item(byte[] sequence, int sequenceLength, ReadMatch[] matches, int numberOfMatches) { this.sequence = copy(sequence, sequenceLength); this.matches = copy(matches, numberOfMatches); } /** * add item if new * * @param sequence * @param sequenceLength * @param matches * @param numberOfMatches * @return true, if added, false if not */ public boolean addIfNew(byte[] sequence, int sequenceLength, ReadMatch[] matches, int numberOfMatches) { Item current = this; while (current != null && !equal(sequence, sequenceLength, current.sequence, current.sequence.length)) { if (current.next == null) { current.next = new Item(sequence, sequenceLength, matches, numberOfMatches); return true; } else current = current.next; } return false; } /** * does this item contain this sequence * * @param sequence * @param sequenceLength * @return true, if this item or any chained to it equals the given one */ public boolean contains(byte[] sequence, int sequenceLength) { Item current = this; while (current != null) { if (equal(sequence, sequenceLength, current.sequence, current.sequence.length)) return true; current = current.next; } return false; } /** * get list of matches for the given sequence2 * * @param sequence * @param sequenceLength * @return matches or null */ public ReadMatch[] getMatches(byte[] sequence, int sequenceLength) { Item current = this; while (current != null) { if (equal(sequence, sequenceLength, current.sequence, current.sequence.length)) return current.matches; current = current.next; } return null; } /** * check whether two strings are equalOverShorterOfBoth * * @param a * @param aLength * @param b * @param bLength * @return true, if equalOverShorterOfBoth */ private boolean equal(byte[] a, int aLength, byte[] b, int bLength) { if (aLength != bLength) return false; for (int i = 0; i < aLength; i++) { if (a[i] != b[i]) return false; } return true; } /** * copy a byte array * * @param array * @param length * @return copy */ private byte[] copy(byte[] array, int length) { byte[] tmp = new byte[length]; System.arraycopy(array, 0, tmp, 0, length); return tmp; } /** * copy a read match array. Makes a copy of each entry * * @param array * @param length * @return read match array copy */ private ReadMatch[] copy(ReadMatch[] array, int length) { ReadMatch[] tmp = new ReadMatch[length]; for (int i = 0; i < length; i++) { tmp[i] = array[i].getCopy(); } return tmp; } } } malt-0.5.2/src/malt/data/ReadMatch.java000066400000000000000000000107401400455127600176110ustar00rootroot00000000000000/* * ReadMatch.java Copyright (C) 2020. Daniel H. Huson * * (Some files contain contributions from other authors, who are then mentioned separately.) * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * */ package malt.data; import jloda.util.Basic; import java.util.Comparator; /** * A match for a given read * Daniel Huson, 8.2014 */ public /** * a read match, consisting of a score, reference ID and the match text */ class ReadMatch { private static long numberOfEntries = 0; private long entryNumber; // used to make all matches unique private float bitScore; private float expected; private int percentIdentity; private int referenceId; private byte[] text; // match text private byte[] rma6Text; private int startRef; // start position of match in reference sequence private int endRef; // end position of match in reference sequence /** * constructor */ public ReadMatch() { } /** * returns a copy * * @return copy */ public ReadMatch getCopy() { return new ReadMatch(bitScore, expected, percentIdentity, referenceId, text, rma6Text, startRef, endRef); } /** * constructor * * @param bitScore * @param referenceId * @param text */ public ReadMatch(float bitScore, float expected, int percentIdentity, int referenceId, byte[] text, byte[] rma6Text, int startRef, int endRef) { this.bitScore = bitScore; this.expected = expected; this.percentIdentity = percentIdentity; this.referenceId = referenceId; this.entryNumber = ++numberOfEntries; this.text = text; this.rma6Text = rma6Text; this.startRef = startRef; this.endRef = endRef; } /** * reuse this object * * @param score * @param referenceId * @param text */ public void set(float score, int referenceId, byte[] text, byte[] rma3Text, int startRef, int endRef) { this.bitScore = score; this.referenceId = referenceId; this.entryNumber = ++numberOfEntries; this.text = text; this.rma6Text = rma3Text; this.startRef = startRef; this.endRef = endRef; } public float getBitScore() { return bitScore; } public float getExpected() { return expected; } public int getPercentIdentity() { return percentIdentity; } public int getReferenceId() { return referenceId; } public byte[] getText() { return text; } public byte[] getRMA6Text() { return rma6Text; } public int getStartRef() { return startRef; } public int getEndRef() { return endRef; } public String toString() { return "RefId=" + referenceId + " bitScore=" + bitScore + " start=" + startRef + " end=" + endRef + " text=" + (text == null ? "null" : Basic.toString(text)); } /** * get comparator */ static public Comparator createComparator() { return (a, b) -> { if (a.bitScore < b.bitScore) return -1; else if (a.bitScore > b.bitScore) return 1; else if (a.referenceId < b.referenceId) return 1; else if (a.referenceId > b.referenceId) return -1; else if (a.entryNumber < b.entryNumber) return -1; else if (a.entryNumber > b.entryNumber) return 1; else return 0; }; } /** * does this overlap the given reference coordinates? * * @param start * @param end * @return overlaps the given coordinates? */ public boolean overlap(int start, int end) { return !(Math.min(startRef, endRef) >= Math.max(start, end) || Math.max(startRef, endRef) <= Math.min(start, end)); } } malt-0.5.2/src/malt/data/ReducedAlphabet.java000066400000000000000000000164121400455127600207770ustar00rootroot00000000000000/* * ReducedAlphabet.java Copyright (C) 2020. Daniel H. Huson * * (Some files contain contributions from other authors, who are then mentioned separately.) * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * */ package malt.data; import jloda.util.Basic; import java.io.IOException; import java.util.HashSet; import java.util.Map; import java.util.Set; import java.util.TreeMap; /** * implements a reduced protein alphabet * Daniel Huson, 8.2014 */ public class ReducedAlphabet implements IAlphabet { private final String description; private final int size; private final byte[] normalizedLetters = { 'X', 'X', 'X', 'X', 'X', 'X', 'X', 'X', 'X', 'X', 'X', 'X', 'X', 'X', 'X', 'X', 'X', 'X', 'X', 'X', 'X', 'X', 'X', 'X', 'X', 'X', 'X', 'X', 'X', 'X', 'X', 'X', 'X', 'X', 'X', 'X', 'X', 'X', 'X', 'X', 'X', 'X', 'X', 'X', 'X', 'X', 'X', 'X', 'X', 'X', 'X', 'X', 'X', 'X', 'X', 'X', 'X', 'X', 'X', 'X', 'X', 'X', 'X', 'X', 'X', 'X', 'X', 'X', 'X', 'X', 'X', 'X', 'X', 'X', 'X', 'X', 'X', 'X', 'X', 'X', 'X', 'X', 'X', 'X', 'X', 'X', 'X', 'X', 'X', 'X', 'X', 'X', 'X', 'X', 'X', 'X', 'X', 'X', 'X', 'X', 'X', 'X', 'X', 'X', 'X', 'X', 'X', 'X', 'X', 'X', 'X', 'X', 'X', 'X', 'X', 'X', 'X', 'X', 'X', 'X', 'X', 'X', 'X', 'X', 'X', 'X', 'X', 'X'}; public static final Map reductions = new TreeMap<>(); static { // From: Bioinformatics. 2009 June 1; 25(11): 1356–1362. Published online 2009 April 7. doi: 10.1093/bioinformatics/btp164: reductions.put("GBMR4", "[ADKERNTSQ] [YFLIVMCWH*X] G P"); reductions.put("SDM12", "A D [KER] N [STQ] [YF] [LIVM*X] C W H G P"); reductions.put("HSDM17", "A D [KE] R N T S Q Y F [LIV*X] M C W H G P"); // Murphy, Lynne Reed and Wallqvist, Anders and Levy, Ronald M., 2000 : reductions.put("BLOSUM50_4", "[LVIMC*X] [AGSTP] [FYW] [EDNQKRH]"); reductions.put("BLOSUM50_8", "[LVIMC*X] [AG] [ST] P [FYW] [EDNQ] [KR] H"); reductions.put("BLOSUM50_10", "[LVIM*X] C A G [ST] P [FYW] [EDNQ] [KR] H"); reductions.put("BLOSUM50_11", "[LVIM*X] C A G S T P [FYW] [EDNQ] [KR] H"); // this was produced from BLOSUM50_10 by separating S and T reductions.put("BLOSUM50_15", "[LVIM*X] C A G S T P [FY] W E D N Q [KR] H"); reductions.put("DIAMOND_11", "[KREDQN*X] C G H [ILV] M F Y W P [STA]"); // DIAMOND default // produced especially for MALT: reductions.put("MALT_10", "[LVIM*X] C [AST] G P [WYF] [DEQ] N [RK] H"); reductions.put("UNREDUCED", "A D K E R N T S Q Y F [L*] I V M C W H G P"); } /** * constructs a reduction protein alphabet mapper * * @param reduction either name or definition of a reduction */ public ReducedAlphabet(String reduction) throws IOException { if (reduction.equalsIgnoreCase("default")) reduction = "DIAMOND_11"; if (Basic.isOneWord(reduction)) { if (!reductions.containsKey(reduction)) throw new IOException("Unknown protein reduction: " + reduction); reduction = reductions.get(reduction); } StringBuilder buffer = new StringBuilder(); char group = 'A'; buffer.append("["); boolean inWhiteSpace = true; for (int i = 0; i < reduction.length(); i++) { int ch = Character.toUpperCase(reduction.charAt(i)); if (Character.isWhitespace(ch)) { if (!inWhiteSpace) { group++; buffer.append("] ["); inWhiteSpace = true; } } else { if (inWhiteSpace) inWhiteSpace = false; if (Character.isLetter(ch) || ch == '*') { normalizedLetters[Character.toLowerCase(ch)] = normalizedLetters[ch] = (byte) group; buffer.append((char) ch); } } } buffer.append("]"); if (normalizedLetters['*'] == 0) normalizedLetters['*'] = '*'; description = buffer.toString(); size = group - 'A' + 1; } /** * maps letter to normalized base or amino acid * * @param letter * @return */ public byte getNormalized(byte letter) { return normalizedLetters[letter]; } /** * do letters a and b correspond to the same base or amino acid? * * @param a * @param b * @return true, if equalOverShorterOfBoth bases */ public boolean equal(byte a, byte b) { return normalizedLetters[a] == normalizedLetters[b]; } /** * gets human-readable description of reduction * * @return string */ public String toString() { return description; } /** * gets the name of this alphabet * * @return name */ public String getName() { return description; } /** * is this a full protein alphabet? * * @return true, if protein */ public boolean isProtein() { return false; } /** * size of alphabet * * @return */ public int size() { return size; } /** * is this a DNA alphabet? * * @return true, if DNA */ public boolean isDNA() { return false; } /** * a reduced protein seed good if it doesn't contain an X * * @param word * @param length * @return */ @Override public boolean isGoodSeed(byte[] word, int length) { for (int i = 0; i < length; i++) { if (word[i] == 'X') return false; } return true; } public static void main(String[] args) throws IOException { Set firstSet = null; for (String name : reductions.keySet()) { Set letters = new HashSet<>(); String def = reductions.get(name); for (int i = 0; i < def.length(); i++) { if (Character.isLetter(def.charAt(i)) || def.charAt(i) == '*') letters.add(def.charAt(i)); } System.err.println(name + ": " + letters.size() + ": " + Basic.toString(letters, ",")); if (firstSet == null) firstSet = letters; else { for (Character ch : letters) { if (!firstSet.contains(ch)) System.err.println("Unexpected letter: " + ch); } } ReducedAlphabet alphabet = new ReducedAlphabet(name); System.err.println("Alphabet: " + alphabet.toString()); System.err.println("Size: " + alphabet.size()); } } } malt-0.5.2/src/malt/data/RefIndex2ClassId.java000066400000000000000000000113121400455127600210060ustar00rootroot00000000000000/* * RefIndex2ClassId.java Copyright (C) 2020. Daniel H. Huson * * (Some files contain contributions from other authors, who are then mentioned separately.) * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * */ package malt.data; import jloda.util.Basic; import jloda.util.ProgressPercentage; import java.io.*; /** * maintains a mapping from reference indices to class ids (e.g. taxon ids or KEGG KOs) * todo: mappings now start at 0, this breaks old Malt * Daniel Huson, 8.2014 */ public class RefIndex2ClassId { private static final byte[] MAGIC_NUMBER = "MAClassV1.1.".getBytes(); private final int maxRefId; private final int[] refIndex2ClassId; public RefIndex2ClassId(int numberOfReferences) { refIndex2ClassId = new int[numberOfReferences]; maxRefId = numberOfReferences; } /** * put, indices start at 0 * * @param refIndex * @param classId */ public void put(int refIndex, int classId) { refIndex2ClassId[refIndex] = classId; } /** * get, indices start at 0 * * @param refIndex * @return class id for given reference id */ public int get(int refIndex) { return refIndex2ClassId[refIndex]; } /** * save to a stream and then close the stream * * @param file * @throws java.io.IOException */ public void save(File file) throws IOException { save(file, MAGIC_NUMBER); } /** * save to a stream and then close the stream * * @param file * @throws java.io.IOException */ public void save(File file, byte[] magicNumber) throws IOException { try (BufferedOutputStream outs = new BufferedOutputStream(new FileOutputStream(file)); ProgressPercentage progressListener = new ProgressPercentage("Writing file: " + file, maxRefId)) { outs.write(magicNumber); // number of entries writeInt(outs, maxRefId); // write headers and sequences: for (int i = 0; i < maxRefId; i++) { writeInt(outs, refIndex2ClassId[i]); // System.err.println("write: "+i+" "+refIndex2ClassId[i]); progressListener.incrementProgress(); } } } /** * constructor from a file * * @param file */ public RefIndex2ClassId(File file) throws IOException { this(file, MAGIC_NUMBER); } /** * constructor from a file * * @param file */ public RefIndex2ClassId(File file, byte[] magicNumber) throws IOException { ProgressPercentage progressListener = null; try (BufferedInputStream ins = new BufferedInputStream(new FileInputStream(file))) { // check magic number: Basic.readAndVerifyMagicNumber(ins, magicNumber); maxRefId = readInt(ins); progressListener = new ProgressPercentage("Reading file: " + file, maxRefId); refIndex2ClassId = new int[maxRefId + 1]; // write headers and sequences: for (int i = 0; i < maxRefId; i++) { refIndex2ClassId[i] = readInt(ins); // System.err.println("read: "+i+" "+refIndex2ClassId[i]); progressListener.incrementProgress(); } } finally { if (progressListener != null) progressListener.close(); } } /** * read an int from an input stream * * @param ins * @return long value * @throws java.io.IOException */ public static int readInt(InputStream ins) throws IOException { return ((ins.read() & 0xFF) << 24) + ((ins.read() & 0xFF) << 16) + ((ins.read() & 0xFF) << 8) + ((ins.read() & 0xFF)); } /** * writes an int value * * @param outs * @param value * @throws java.io.IOException */ public static void writeInt(OutputStream outs, int value) throws IOException { outs.write((byte) (value >> 24)); outs.write((byte) (value >> 16)); outs.write((byte) (value >> 8)); outs.write((byte) value); } } malt-0.5.2/src/malt/data/ReferencesDBAccess.java000066400000000000000000000136371400455127600214020ustar00rootroot00000000000000/* * ReferencesDBAccess.java Copyright (C) 2020. Daniel H. Huson * * (Some files contain contributions from other authors, who are then mentioned separately.) * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * */ package malt.data; import jloda.util.Basic; import jloda.util.FileLineIterator; import malt.MaltOptions; import megan.io.*; import megan.io.experimental.ByteFileGetterPagedMemory; import megan.io.experimental.LongFileGetterPagedMemory; import java.io.Closeable; import java.io.File; import java.io.IOException; /** * accesses the references DB * Daniel Huson, 3.2015 */ public class ReferencesDBAccess implements Closeable { private final byte[][] headers; private final byte[][] sequences; private int numberOfSequences; private long numberOfLetters; private static final int SYNC_BITMASK = 1023;// length of vector must be SYNC_BITMASK+1 private final Object[] syncObjects; private final ILongGetter refIndex; private final IByteGetter refDB; /** * construct from an input file * * @param refIndexFile * @throws java.io.IOException */ public ReferencesDBAccess(MaltOptions.MemoryMode memoryMode, File refIndexFile, File refDBFile, File refInfFile) throws IOException { syncObjects = new Object[SYNC_BITMASK + 1]; for (int i = 0; i < syncObjects.length; i++) { syncObjects[i] = new Object(); } switch (memoryMode) { default: case load: refIndex = new LongFileGetterInMemory(refIndexFile); refDB = new ByteFileGetterInMemory(refDBFile); break; case page: refIndex = new LongFileGetterPagedMemory(refIndexFile); refDB = new ByteFileGetterPagedMemory(refDBFile); break; case map: refIndex = new LongFileGetterMappedMemory(refIndexFile); refDB = new ByteFileGetterMappedMemory(refDBFile); break; } try (FileLineIterator it = new FileLineIterator(refInfFile)) { while (it.hasNext()) { String aLine = it.next(); if (aLine.startsWith("sequences")) { numberOfSequences = Integer.parseInt(Basic.getTokenFromTabSeparatedLine(aLine, 1)); } else if (aLine.startsWith("letters")) { numberOfLetters = Long.parseLong(Basic.getTokenFromTabSeparatedLine(aLine, 1)); } } } System.err.printf("Number of sequences:%,14d%n", numberOfSequences); System.err.printf("Number of letters:%,16d%n", numberOfLetters); if (numberOfSequences != refIndex.limit()) throw new IOException("Expected " + numberOfSequences + "sequences , index contains: " + refIndex.limit()); headers = new byte[numberOfSequences][]; sequences = new byte[numberOfSequences][]; } /** * Get header string. Index starts at 0 * * @param index * @return header */ public byte[] getHeader(int index) throws IOException { byte[] array = headers[index]; if (array == null) { synchronized (syncObjects[index & SYNC_BITMASK]) { if (headers[index] == null) { long dbIndex = refIndex.get(index); dbIndex += 4 + refDB.getInt(dbIndex); // increment dbIndex by 4 plus length of sequence (to skip over sequence) int headerLength = refDB.getInt(dbIndex); dbIndex += 4; array = new byte[headerLength]; refDB.get(dbIndex, array, 0, headerLength); headers[index] = array; } else array = headers[index]; } } return array; } /** * Get sequence. Index starts at 0 * * @param index * @return sequence */ public byte[] getSequence(int index) throws IOException { byte[] array = sequences[index]; if (array == null) { synchronized (syncObjects[index & SYNC_BITMASK]) { if (sequences[index] == null) { long dbIndex = refIndex.get(index); int sequenceLength = refDB.getInt(dbIndex); dbIndex += 4; array = new byte[sequenceLength]; refDB.get(dbIndex, array, 0, sequenceLength); sequences[index] = array; } else array = sequences[index]; } } return array; } /** * Get sequence length * * @param index * @return sequence length */ public int getSequenceLength(int index) throws IOException { if (sequences[index] != null) return sequences[index].length; else return refDB.getInt(refIndex.get(index)); } /** * number of sequences * * @return number of sequences */ public int getNumberOfSequences() { return numberOfSequences; } /** * total number of letters * * @return number of letters */ public long getNumberOfLetters() { return numberOfLetters; } /** * close */ public void close() { refIndex.close(); refDB.close(); } } malt-0.5.2/src/malt/data/ReferencesDBBuilder.java000066400000000000000000000221751400455127600215640ustar00rootroot00000000000000/* * ReferencesDBBuilder.java Copyright (C) 2020. Daniel H. Huson * * (Some files contain contributions from other authors, who are then mentioned separately.) * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * */ package malt.data; import jloda.util.Basic; import jloda.util.CanceledException; import jloda.util.ProgressPercentage; import malt.io.FastAFileIteratorBytes; import megan.io.OutputWriter; import java.io.*; import java.util.Iterator; import java.util.List; /** * builds the reference sequences database * Daniel Huson, 8.2014 */ public class ReferencesDBBuilder implements ISequenceAccessor { private byte[][] headers; private byte[][] sequences; private int numberOfSequences; private long numberOfLetters; /** * constructor */ public ReferencesDBBuilder() { headers = new byte[10000][]; sequences = new byte[10000][]; numberOfSequences = 0; numberOfLetters = 0; } /** * resize * * @param newSize */ public void grow(int newSize) { if (newSize > headers.length) { byte[][] newHeaders = new byte[newSize][]; int top = Math.min(newSize, headers.length); System.arraycopy(headers, 0, newHeaders, 0, top); headers = newHeaders; byte[][] newSequences = new byte[newSize][]; top = Math.min(newSize, sequences.length); System.arraycopy(sequences, 0, newSequences, 0, top); sequences = newSequences; } } /** * add a header and sequence to the list of sequences * * @param header * @param sequence */ public void add(byte[] header, byte[] sequence) { if (numberOfSequences == sequences.length) { headers = grow(headers); sequences = grow(sequences); } headers[numberOfSequences] = header; sequences[numberOfSequences] = sequence; numberOfSequences++; numberOfLetters += sequence.length; } /** * grow an array * * @param array * @return bigger array */ private byte[][] grow(byte[][] array) { byte[][] result = new byte[Math.min(Integer.MAX_VALUE, 2 * Math.max(1, array.length))][]; System.arraycopy(array, 0, result, 0, array.length); return result; } /** * Get header string. Index starts at 0 * * @param index * @return header */ public byte[] getHeader(int index) { return headers[index]; } /** * gets an iterable over all ref names as strings * * @return iterable */ public Iterable refNames() { return () -> new Iterator<>() { private int i = 0; @Override public boolean hasNext() { return i < numberOfSequences; } @Override public String next() { return Basic.getAccessionWord(headers[i++]); } @Override public void remove() { } }; } /** * Get sequence. Index starts at 0 * * @param index * @return sequence */ public byte[] getSequence(int index) { return sequences[index]; } /** * load a collection of fastA files * * @param fileNames * @throws IOException * @throws CanceledException */ public void loadFastAFiles(final List fileNames, final IAlphabet alphabet) throws IOException { long totalSize = 0; for (String fileName : fileNames) { totalSize += (new File(fileName)).length(); } int guessNumberOfSequences = (int) Math.min(Integer.MAX_VALUE, totalSize / 1000L); grow(guessNumberOfSequences); try (ProgressPercentage progress = new ProgressPercentage("Loading FastA files:", fileNames.size())) { for (String fileName : fileNames) { loadFastAFile(fileName, alphabet); progress.incrementProgress(); } } } /** * load data from a fastA file * * @param fileName * @throws FileNotFoundException */ private void loadFastAFile(final String fileName, final IAlphabet alphabet) throws IOException { try (FastAFileIteratorBytes it = new FastAFileIteratorBytes(fileName, alphabet)) { while (it.hasNext()) { byte[] header = it.next(); if (it.hasNext()) { byte[] sequence = it.next(); add(header, sequence); } } } } /** * save sequences in fastA format * * @param fileName * @throws IOException * @throws CanceledException */ public void saveFastAFile(String fileName) throws IOException { try (BufferedWriter w = new BufferedWriter(new FileWriter(fileName), 8192)) { for (int i = 0; i < numberOfSequences; i++) { w.write(Basic.toString(headers[i]) + "\n"); w.write(Basic.toString(sequences[i]) + "\n"); } } } /** * Save the reference data as an index file and a datafile * * @param refIndexFile * @param refDBFile * @throws IOException * @throws CanceledException */ public void save(File refIndexFile, File refDBFile, File refInfFile, boolean saveFirstWordOnly) throws IOException, CanceledException { System.err.println("Writing file: " + refDBFile); try (ProgressPercentage progress = new ProgressPercentage("Writing file: " + refIndexFile, numberOfLetters); final OutputWriter refDBOutputStream = new OutputWriter(refDBFile); OutputWriter refIndexOutputStream = new OutputWriter(refIndexFile)) { long dbFilePos = 0; for (int i = 0; i < numberOfSequences; i++) { refIndexOutputStream.writeLong(dbFilePos); final byte[] sequence = sequences[i]; refDBOutputStream.writeInt(sequence.length); refDBOutputStream.write(sequence); dbFilePos += 4 + sequence.length; final byte[] header = (saveFirstWordOnly ? getFirstWord(headers[i]) : headers[i]); refDBOutputStream.writeInt(header.length); refDBOutputStream.write(header); dbFilePos += 4 + header.length; progress.incrementProgress(); } } try (BufferedWriter w = new BufferedWriter(new FileWriter(refInfFile))) { w.write("sequences\t" + numberOfSequences + "\n"); w.write("letters\t" + numberOfLetters + "\n"); } } /** * get string consisting of first word * * @param str * @return first word */ static public byte[] getFirstWord(byte[] str) { for (int i = 0; i < str.length; i++) { if (Character.isWhitespace(str[i])) { byte[] result = new byte[i]; System.arraycopy(str, 0, result, 0, i); return result; } } return str; } /** * number of sequences * * @return number of sequences */ public int getNumberOfSequences() { return numberOfSequences; } /** * total number of letters * * @return number of letters */ public long getNumberOfLetters() { return numberOfLetters; } /** * extend the header by the given tag. We use this to write the taxon id into a reference sequence * * @param index * @param tag * @param id */ public void extendHeader(int index, String tag, Integer id) { byte[] header = headers[index]; int pos = 0; while (Character.isWhitespace(header[pos]) && pos < header.length - 1) // skip leading white space pos++; while (!Character.isWhitespace(header[pos]) && pos < header.length - 1) // go to next white space or end pos++; byte[] add; if (header[pos - 1] == '|') add = String.format("%s%d", tag, id).getBytes(); else add = String.format("|%s%d", tag, id).getBytes(); byte[] newHeader = new byte[header.length + add.length]; System.arraycopy(header, 0, newHeader, 0, pos); System.arraycopy(add, 0, newHeader, pos, add.length); if (pos < header.length - 1) { System.arraycopy(header, pos, newHeader, add.length + pos, header.length - pos); } headers[index] = newHeader; //System.err.println("Header="+Basic.toString(headers[index])); } } malt-0.5.2/src/malt/data/ReferencesHashTableAccess.java000066400000000000000000000250641400455127600227450ustar00rootroot00000000000000/* * ReferencesHashTableAccess.java Copyright (C) 2020. Daniel H. Huson * * (Some files contain contributions from other authors, who are then mentioned separately.) * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * */ package malt.data; import jloda.thirdparty.MurmurHash3; import jloda.util.Basic; import jloda.util.ProgressPercentage; import malt.MaltOptions; import malt.util.Utilities; import megan.io.*; import megan.io.experimental.IntFileGetterPagedMemory; import megan.io.experimental.LongFileGetterPagedMemory; import java.io.*; /** * hash table used for mapping k-mers to sequences and offsets (given by a pair of integers) * Daniel Huson, 8.2014 */ public class ReferencesHashTableAccess implements Closeable { public static final int BUFFER_SIZE = 8192; // benchmarking suggested that choosing a large size doesn't make a difference private final ILongGetter tableIndexGetter; // each entry points to a row of integers that is contained in the data table private final int tableSize; private final int hashMask; private final int randomNumberSeed; private long theSize = 0; // counts items private final IAlphabet seedAlphabet; // alphabet used by seeds private final SeedShape seedShape; // seed shape that is saved and loaded from index private final IIntGetter tableDataGetter; // used for memory mapping /** * construct the table from the given directory * * @param indexDirectory */ public ReferencesHashTableAccess(MaltOptions.MemoryMode memoryMode, String indexDirectory, int tableNumber) throws IOException { final File indexFile = new File(indexDirectory, "index" + tableNumber + ".idx"); final File tableIndexFile = new File(indexDirectory, "table" + tableNumber + ".idx"); final File tableDataFile = new File(indexDirectory, "table" + tableNumber + ".db"); try (DataInputStream ins = new DataInputStream(new BufferedInputStream(new FileInputStream(indexFile), BUFFER_SIZE))) { ProgressPercentage progress = new ProgressPercentage("Reading file: " + indexFile); Basic.readAndVerifyMagicNumber(ins, ReferencesHashTableBuilder.MAGIC_NUMBER); SequenceType referenceSequenceType = SequenceType.valueOf(ins.readInt()); System.err.println("Reference sequence type: " + referenceSequenceType.toString()); if (referenceSequenceType == SequenceType.Protein) { int length = ins.readInt(); byte[] reductionBytes = new byte[length]; if (ins.read(reductionBytes, 0, length) != length) throw new IOException("Read failed"); seedAlphabet = new ReducedAlphabet(Basic.toString(reductionBytes)); System.err.println("Protein reduction: " + seedAlphabet); } else seedAlphabet = DNA5.getInstance(); // get all sizes: tableSize = ins.readInt(); // get mask used in hashing hashMask = ins.readInt(); randomNumberSeed = ins.readInt(); theSize = ins.readLong(); final int stepSize = ins.readInt(); if (stepSize > 1) System.err.println("Index was built using stepSize=" + stepSize); { int length = ins.readInt(); byte[] shapeBytes = new byte[length]; if (ins.read(shapeBytes, 0, length) != length) throw new IOException("Read failed"); seedShape = new SeedShape(seedAlphabet, shapeBytes); } progress.reportTaskCompleted(); } switch (memoryMode) { default: case load: tableIndexGetter = new LongFileGetterInMemory(tableIndexFile); tableDataGetter = new IntFileGetterInMemory(tableDataFile); break; case page: tableIndexGetter = new LongFileGetterPagedMemory(tableIndexFile); tableDataGetter = new IntFileGetterPagedMemory(tableDataFile); break; case map: tableIndexGetter = new LongFileGetterMappedMemory(tableIndexFile); tableDataGetter = new IntFileGetterMappedMemory(tableDataFile); break; } } /** * lookup all entries for a given key and put them in the given row object. If none found, row is set to empty * todo: re-implement this * * @param key * @param row */ public int lookup(byte[] key, Row row) throws IOException { int hashValue = getHash(key); if (hashValue >= 0 && hashValue < tableIndexGetter.limit() && setRow(tableIndexGetter.get(hashValue), row)) return row.size(); row.setEmpty(); return 0; } /** * get the hash value * * @param key * @return hash value */ public int getHash(byte[] key) { int value = MurmurHash3.murmurhash3x8632(key, 0, key.length, randomNumberSeed) & hashMask; if (value >= Basic.MAX_ARRAY_SIZE) // only use modulo if we are on or above table size value %= Basic.MAX_ARRAY_SIZE; return value; } /** * get the number of entries * * @return number of entries */ public long size() { return theSize; } /** * get the seed shape associated with this table * * @return seed shape */ public SeedShape getSeedShape() { return seedShape; } /** * show the whole hash table in human readable form * * @throws java.io.IOException */ public void show() throws IOException { System.err.println("Table (" + tableSize + "):"); Row row = new Row(); for (int z = 0; z < tableIndexGetter.limit(); z++) { if (z > 50) continue; System.err.print("hash " + z + " -> "); if (setRow(tableIndexGetter.get(z), row)) { System.err.print("(" + row.size() / 2 + ")"); for (int i = 0; i < row.size(); i += 2) { if (i > 100) { System.err.print(" ..."); break; } System.err.print(" " + row.get(i) + "/" + row.get(i + 1)); } } System.err.println(); } } /** * set the row for the given location * * @param location * @param row * @return false, if location invalid */ private boolean setRow(long location, Row row) throws IOException { if (location == 0) return false; if (location < 0) { location = -location; row.setPair((int) (location >> 32), (int) location); // is a singleton entry } else { int length = tableDataGetter.get(location); // length is number int's that follow this first int that tells us the length if (row.tmpArray.length <= length) row.tmpArray = new int[length + 1]; row.tmpArray[0] = length; for (int i = 1; i <= length; i++) row.tmpArray[i] = tableDataGetter.get(location + i); row.set(row.tmpArray, 0); } return true; } /** * get alphabet used for seeds. Note that the seed alphabet may differ from the query alphabet i.e. when using a protein reduction alphabet for seeding * * @return seed alphabet */ public IAlphabet getSeedAlphabet() { return seedAlphabet; } /** * make sure that we can reads the files * * @param indexDirectory * @throws IOException */ public static void checkFilesExist(String indexDirectory, int tableNumber) throws IOException { Utilities.checkFileExists(new File(indexDirectory)); Utilities.checkFileExists(new File(indexDirectory, "index" + tableNumber + ".idx")); Utilities.checkFileExists(new File(indexDirectory, "table" + tableNumber + ".idx")); Utilities.checkFileExists(new File(indexDirectory, "table" + tableNumber + ".db")); } /** * determines the number of tables existing in the index * * @param indexDirectory * @return number of tables */ public static int determineNumberOfTables(String indexDirectory) { int tableNumber = 0; while ((new File(indexDirectory, "index" + tableNumber + ".idx")).exists()) { tableNumber++; } return tableNumber; } /** * show part of the hash table in human readable form * * @throws java.io.IOException */ public void showAPart() throws IOException { final Row row = new Row(); System.err.println("Seed table (" + tableIndexGetter.limit() + "):"); for (int z = 0; z < tableIndexGetter.limit(); z++) { if (z > 10) continue; System.err.print("hash " + z + " -> "); if (setRow(tableIndexGetter.get(z), row)) { System.err.print("(" + row.size() / 2 + ")"); for (int i = 0; i < row.size(); i += 2) { if (i > 100) { System.err.print(" ..."); break; } System.err.print(" " + row.get(i) + "/" + row.get(i + 1)); } } System.err.println(); } } /** * construct the table from the given directory * * @param indexDirectory */ public static SequenceType getIndexSequenceType(String indexDirectory) throws IOException { File indexFile = new File(indexDirectory, "index0.idx"); try (DataInputStream ins = new DataInputStream(new BufferedInputStream(new FileInputStream(indexFile), 8192))) { Basic.readAndVerifyMagicNumber(ins, ReferencesHashTableBuilder.MAGIC_NUMBER); return SequenceType.valueOf(ins.readInt()); } } public void close() { tableIndexGetter.close(); tableDataGetter.close(); } } malt-0.5.2/src/malt/data/ReferencesHashTableBuilder.java000066400000000000000000000523111400455127600231250ustar00rootroot00000000000000/* * ReferencesHashTableBuilder.java Copyright (C) 2020. Daniel H. Huson * * (Some files contain contributions from other authors, who are then mentioned separately.) * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * */ package malt.data; import jloda.thirdparty.MurmurHash3; import jloda.util.Basic; import jloda.util.ProgressPercentage; import jloda.util.Single; import malt.util.Utilities; import megan.io.IntFilePutter; import megan.io.OutputWriter; import java.io.File; import java.io.IOException; import java.util.Random; import java.util.concurrent.CountDownLatch; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; /** * builds the reference hash table * Daniel Huson, 8.2014 */ public class ReferencesHashTableBuilder { public static final byte[] MAGIC_NUMBER = "MATableV0.12.".getBytes(); private final SequenceType referenceSequenceType; private final IAlphabet alphabet; private long[] tableIndex; // main table index private IntFilePutter tableDataPutter; // main table data private final int tableSize; private final int hashMask; // use bit mask rather than modulo (five times as fast) private final int randomNumberSeed; private long theSize = 0; // counts items private final int maxHitsPerHash; // this was 10000000 private final SeedShape seedShape; // seed shape that is saved and loaded from index private final int SYNC_BITMASK = 1023; // use lots of objects to synchronize on so that threads don't in each others way private final Object[] syncObjects = new Object[SYNC_BITMASK + 1]; private final int stepSize; /** * constructor * * @param seedShape * @param numberOfSequences * @param numberOfLetters * @param randomNumberSeed */ public ReferencesHashTableBuilder(SequenceType referenceSequenceType, IAlphabet alphabet, SeedShape seedShape, int numberOfSequences, long numberOfLetters, int randomNumberSeed, int maxHitPerSeed, float hashTableLoadFactor, int stepSize) throws IOException { this.referenceSequenceType = referenceSequenceType; this.alphabet = alphabet; this.seedShape = seedShape; this.randomNumberSeed = randomNumberSeed; this.stepSize = stepSize; // total is numberOfLetters minus last letter of each sequence divided by stepSize final long totalNumberOfSeeds = (long) (Math.ceil((numberOfLetters - (numberOfSequences * (seedShape.getLength() - 1))) / stepSize)); // number of possible different seed values: final long numberOfPossibleHashValues = (long) Math.ceil(Math.pow(alphabet.size(), seedShape.getWeight())); System.err.printf("Seeds found: %,14d%n", totalNumberOfSeeds); // System.err.println("Number of possible hash values: " + numberOfPossibleHashValues); long entriesPerTable = (long) (hashTableLoadFactor * Math.min(totalNumberOfSeeds, numberOfPossibleHashValues)); // assume only 90% are used if (entriesPerTable >= Integer.MAX_VALUE / 2) { tableSize = Basic.MAX_ARRAY_SIZE; hashMask = Integer.MAX_VALUE; } else { long size = 1; while (entriesPerTable > size) { size *= 2; } tableSize = (int) size; hashMask = tableSize - 1; } System.err.printf("tableSize= %,14d%n", tableSize); System.err.printf("hashMask.length=%d%n", Integer.toBinaryString(hashMask).length()); maxHitsPerHash = maxHitPerSeed; // we use the same value because the actual number of seeds used is usually smaller than the table size // final double averageWordsPerHashValue = Math.max(1, (totalNumberOfSeeds / (double) tableSize)); // maxHitsPerHash = (int)Math.max(1, maxHitPerSeed * averageWordsPerHashValue); System.err.println("maxHitsPerHash set to: " + maxHitsPerHash); final ProgressPercentage progress = new ProgressPercentage("Initializing arrays..."); for (int i = 0; i < syncObjects.length; i++) { syncObjects[i] = new Object(); } progress.reportTaskCompleted(); } /** * build the hash table * * @param referencesDB * @param numberOfThreads */ public void buildTable(final File tableIndexFile, final File tableDataFile, final ReferencesDBBuilder referencesDB, int numberOfThreads, boolean buildTableInMemory) throws IOException { tableIndex = new long[tableSize]; countSeeds(referencesDB, numberOfThreads); long limit = allocateTable(numberOfThreads); tableDataPutter = new IntFilePutter(tableDataFile, limit + 1, buildTableInMemory); // limit+1 because we start with index 1 fillTable(referencesDB, numberOfThreads); randomizeBuildRows(numberOfThreads); saveTableIndex(tableIndex, tableIndexFile); tableIndex = null; tableDataPutter.close(); } /** * save the table index * * @param tableIndex * @param tableIndexFile * @throws IOException */ private void saveTableIndex(long[] tableIndex, File tableIndexFile) throws IOException { final ProgressPercentage progress = new ProgressPercentage("Writing file: " + tableIndexFile, tableIndex.length); try (OutputWriter outs = new OutputWriter(tableIndexFile)) { for (long value : tableIndex) { outs.writeLong(value); progress.incrementProgress(); } } progress.close(); } /** * count the seeds. He we use forwardTable and reverseTable to hold the counts, later the counts are replaced by locations * * @param referencesDB * @param numberOfThreads0 */ private void countSeeds(final ReferencesDBBuilder referencesDB, int numberOfThreads0) { final int numberOfThreads = Math.min(referencesDB.getNumberOfSequences(), numberOfThreads0); final ProgressPercentage progressPercentage = new ProgressPercentage("Analysing seeds...", referencesDB.getNumberOfSequences()); final int[] countsForProgress = new int[numberOfThreads]; final long[] countLowComplexitySeeds = new long[numberOfThreads]; final ExecutorService executor = Executors.newFixedThreadPool(numberOfThreads); final CountDownLatch countDownLatch = new CountDownLatch(numberOfThreads); try { // launch the worker threads for (int i = 0; i < numberOfThreads; i++) { final int threadNumber = i; executor.execute(() -> { try { final byte[] seedBytes = seedShape.createBuffer(); for (int refIndex = threadNumber; refIndex < referencesDB.getNumberOfSequences(); refIndex += numberOfThreads) { byte[] sequence = referencesDB.getSequence(refIndex); int top = sequence.length - seedShape.getLength() + 1; for (int pos = 0; pos < top; pos += stepSize) { seedShape.getSeed(sequence, pos, seedBytes); if (!Utilities.hasAtMostTwoLetters(seedBytes)) { int hashValue = getHash(seedBytes); synchronized (syncObjects[hashValue & SYNC_BITMASK]) { if (tableIndex[hashValue] <= maxHitsPerHash) tableIndex[hashValue]++; } } else countLowComplexitySeeds[threadNumber]++; } countsForProgress[threadNumber]++; } } finally { countDownLatch.countDown(); } }); } // wait for jobs to complete: while (countDownLatch.getCount() > 0) { try { Thread.sleep(500); // sleep and then report progress } catch (InterruptedException e) { Basic.caught(e); break; } progressPercentage.setProgress(Basic.getSum(countsForProgress)); } progressPercentage.close(); System.err.printf("Number of low-complexity seeds skipped: %,d%n", Basic.getSum(countLowComplexitySeeds)); } finally { executor.shutdown(); } } /** * allocate the hash table * * @param numberOfThreads0 */ private long allocateTable(final int numberOfThreads0) throws IOException { final int numberOfThreads = Math.min(tableSize, numberOfThreads0); ProgressPercentage progressPercentage = new ProgressPercentage("Allocating hash table...", tableSize); final int[] countsForProgress = new int[numberOfThreads]; final long[] totalKeys = new long[numberOfThreads]; final long[] totalSeeds = new long[numberOfThreads]; final long[] totalDropped = new long[numberOfThreads]; final Single nextFreeIndex = new Single<>(1L); final ExecutorService executor = Executors.newFixedThreadPool(numberOfThreads); final CountDownLatch countDownLatch = new CountDownLatch(numberOfThreads); try { // launch the worker threads for (int i = 0; i < numberOfThreads; i++) { final int threadNumber = i; executor.execute(() -> { try { for (long index = threadNumber; index < tableSize; index += numberOfThreads) { final long count = tableIndex[(int) index]; // here count is number of seeds that will be saved for given index if (count > maxHitsPerHash) { tableIndex[(int) index] = 0L; // need to overwrite the count totalDropped[threadNumber] += count; } else if (count > 1) { totalSeeds[threadNumber] += count; totalKeys[threadNumber]++; synchronized (nextFreeIndex) { final long location = nextFreeIndex.get(); tableIndex[(int) index] = location; nextFreeIndex.set(location + 2 * count + 1); } } else if (count == 1) { // will write refInd and offset directly into table, use value of -1 to indicate this totalSeeds[threadNumber]++; totalKeys[threadNumber]++; tableIndex[(int) index] = -1L; } else if (count < 0) throw new IOException("negative count: " + count); countsForProgress[threadNumber]++; } } catch (Exception ex) { Basic.caught(ex); System.exit(1); } finally { countDownLatch.countDown(); } }); } // wait for jobs to complete: while (countDownLatch.getCount() > 0) { try { Thread.sleep(500); // sleep and then report progress } catch (InterruptedException e) { Basic.caught(e); break; } progressPercentage.setProgress(Basic.getSum(countsForProgress)); } progressPercentage.reportTaskCompleted(); System.err.printf("Total keys used: %,14d%n", Basic.getSum(totalKeys)); System.err.printf("Total seeds matched:%,14d%n", Basic.getSum(totalSeeds)); System.err.printf("Total seeds dropped:%,14d%n", Basic.getSum(totalDropped)); } finally { executor.shutdownNow(); } return nextFreeIndex.get(); } /** * Fill the hash table * * @param referencesDB * @param numberOfThreads0 */ private void fillTable(final ReferencesDBBuilder referencesDB, int numberOfThreads0) { final int numberOfThreads = Math.min(referencesDB.getNumberOfSequences(), numberOfThreads0); // populate the table final ProgressPercentage progressPercentage = new ProgressPercentage("Filling hash table...", referencesDB.getNumberOfSequences()); final int[] countsForProgress = new int[numberOfThreads]; final long[] counts = new long[numberOfThreads]; final ExecutorService executor = Executors.newFixedThreadPool(numberOfThreads); final CountDownLatch countDownLatch = new CountDownLatch(numberOfThreads); try { for (int i = 0; i < numberOfThreads; i++) { final int threadNumber = i; executor.execute(() -> { try { final byte[] seedBytes = seedShape.createBuffer(); for (int refIndex = threadNumber; refIndex < referencesDB.getNumberOfSequences(); refIndex += numberOfThreads) { final byte[] sequence = referencesDB.getSequence(refIndex); final int top = sequence.length - seedShape.getLength() + 1; for (int pos = 0; pos < top; pos += stepSize) { seedShape.getSeed(sequence, pos, seedBytes); if (!Utilities.hasAtMostTwoLetters(seedBytes)) { final int hashValue = getHash(seedBytes); synchronized (syncObjects[hashValue & SYNC_BITMASK]) { final long location = tableIndex[hashValue]; if (location == -1) { // has been marked as singleton, so store value directly final long value = -(((long) refIndex << 32) | pos); tableIndex[hashValue] = value; } else if (location > 0) { final int length = tableDataPutter.get(location); tableDataPutter.put(location, length + 2); tableDataPutter.put(location + length + 1, refIndex); tableDataPutter.put(location + length + 2, pos); } } counts[threadNumber]++; } } countsForProgress[threadNumber]++; } } finally { countDownLatch.countDown(); } }); } // wait for jobs to complete: while (countDownLatch.getCount() > 0) { try { Thread.sleep(500); // sleep and then report progress } catch (InterruptedException e) { Basic.caught(e); break; } progressPercentage.setProgress(Basic.getSum(countsForProgress)); } progressPercentage.reportTaskCompleted(); } finally { executor.shutdownNow(); } theSize = Basic.getSum(counts); } /** * randomize the rows of the table, parallel version * * @param numberOfThreads */ private void randomizeBuildRows(final int numberOfThreads) { final ProgressPercentage progressPercentage = new ProgressPercentage("Randomizing rows...", tableSize); final int[] countsForProgress = new int[numberOfThreads]; final ExecutorService executor = Executors.newFixedThreadPool(numberOfThreads); final CountDownLatch countDownLatch = new CountDownLatch(numberOfThreads); try { for (int i = 0; i < numberOfThreads; i++) { final int threadNumber = i; executor.execute(() -> { try { final Random random = new Random(); for (long index = threadNumber; index < tableSize; index += numberOfThreads) { // need to use long otherwise can get overflow if (index < tableIndex.length) { long location = tableIndex[(int) index]; if (location > 0) { int size = tableDataPutter.get(location); if (size > 2) { random.setSeed(index * index); // use location in hash table as seed. Utilities.randomizePairs(tableDataPutter, location + 1, size, random); } } } countsForProgress[threadNumber]++; } } catch (Exception ex) { Basic.caught(ex); } finally { countDownLatch.countDown(); } }); } // wait for all tasks to be completed: while (countDownLatch.getCount() > 0) { try { Thread.sleep(100); // sleep and then report progress } catch (InterruptedException e) { Basic.caught(e); break; } progressPercentage.setProgress(Basic.getSum(countsForProgress)); } progressPercentage.reportTaskCompleted(); } finally { executor.shutdownNow(); } } /** * for a given key, add the reference id and sequence offset to table * uses very naive synchronization * * @param key * @return hash value */ public int getHash(byte[] key) { int value = MurmurHash3.murmurhash3x8632(key, 0, key.length, randomNumberSeed) & hashMask; // & also removes negative sign if (value >= Basic.MAX_ARRAY_SIZE) value %= Basic.MAX_ARRAY_SIZE; return value; } /** * get the number of entries * * @return number of entries */ public long size() { return theSize; } /** * save master index file * * @param file * @throws IOException */ public void saveIndexFile(File file) throws IOException { final ProgressPercentage progressPercentage = new ProgressPercentage("Writing file: " + file); try (OutputWriter outs = new OutputWriter(file)) { outs.write(MAGIC_NUMBER); outs.writeInt(SequenceType.rankOf(referenceSequenceType)); if (referenceSequenceType == SequenceType.Protein) { final byte[] bytes = alphabet.toString().getBytes(); outs.writeInt(bytes.length); outs.write(bytes); } outs.writeInt(tableSize); outs.writeInt(hashMask); outs.writeInt(randomNumberSeed); outs.writeLong(theSize); outs.writeInt(stepSize); final byte[] shapeBytes = seedShape.getBytes(); outs.writeInt(shapeBytes.length); outs.write(shapeBytes); } finally { progressPercentage.reportTaskCompleted(); } } /** * make sure that we can write the files * * @param indexDirectory * @throws IOException */ public static void checkCanWriteFiles(String indexDirectory, int tableNumber) throws IOException { final File indexFile = new File(indexDirectory, "index" + tableNumber + ".idx"); if ((!indexFile.exists() || indexFile.delete()) && !indexFile.createNewFile()) throw new IOException("Can't create file: " + indexFile); final File tableIndexFile = new File(indexDirectory, "table" + tableNumber + ".idx"); if ((!tableIndexFile.exists() || tableIndexFile.delete()) && !tableIndexFile.createNewFile()) throw new IOException("Can't create file: " + tableIndexFile); final File tableDBFile = new File(indexDirectory, "table" + tableNumber + ".db"); if ((!tableDBFile.exists() || tableDBFile.delete()) && !tableDBFile.createNewFile()) throw new IOException("Can't create file: " + tableDBFile); } } malt-0.5.2/src/malt/data/Row.java000066400000000000000000000057071400455127600165370ustar00rootroot00000000000000/* * Row.java Copyright (C) 2020. Daniel H. Huson * * (Some files contain contributions from other authors, who are then mentioned separately.) * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * */ package malt.data; /** * a row of numbers that are stored in a larger array. * Daniel Huson, 8.2014 */ public class Row { private int size; private int offset; private int[] containingArray; public int[] tmpArray = new int[10000]; // // todo temporary array used during implementation of memory mapped index /** * constructor */ public Row() { } /** * Set the row. array[offset] must contain size, i.e. the number of integers to be used * array[offset+1]... array[offset+size-1] are the numbers * * @param array array containing size followed by entries * @param offset location of size entry in array */ public void set(int[] array, int offset) { this.size = array[offset]; this.containingArray = array; this.offset = offset + 1; } /** * setting a single pair of numbers * * @param refId * @param position */ public void setPair(int refId, int position) { size = 2; offset = 0; tmpArray[0] = refId; tmpArray[1] = position; containingArray = tmpArray; } /** * set to empty */ public void setEmpty() { size = 0; } /** * gets the number of int in this row * * @return size */ public int size() { return size; } /** * use this to access numbers 0,..,size-1 * * @param index * @return item */ public int get(int index) { return containingArray[offset + index]; } /** * get offset at which numbers start (position of size entry plus 1) * * @return offset */ public int getOffset() { return offset; } /** * get string representation * * @return */ public String toString() { if (size > 0) { final StringBuilder buf = new StringBuilder(); buf.append("(").append(size()).append("): "); for (int i = 0; i < size(); i += 2) buf.append(" ").append(get(i)).append("/").append(get(i + 1)); return buf.toString(); } else return "null"; } } malt-0.5.2/src/malt/data/SeedMatch.java000066400000000000000000000075761400455127600176330ustar00rootroot00000000000000/* * SeedMatch.java Copyright (C) 2020. Daniel H. Huson * * (Some files contain contributions from other authors, who are then mentioned separately.) * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * */ package malt.data; /** * A seed match as used in the inner loop of the alignment engine * Daniel Huson, 8.2014 */ import java.util.Comparator; /** * a seed match, consisting of a location in a query and in a reference */ public class SeedMatch { private int queryOffset; private int referenceOffset; private int rank; // rank of frame. Frame is given by frame[rank] private int seedLength; static private final Comparator comparator = (a, b) -> { if (a.queryOffset < b.queryOffset) return -1; else if (a.queryOffset > b.queryOffset) return 1; else if (a.referenceOffset < b.referenceOffset) return -1; else if (a.referenceOffset > b.referenceOffset) return 1; else if (a.rank < b.rank) return -1; else if (a.rank > b.rank) return 1; else if (a.seedLength < b.seedLength) return -1; else if (a.seedLength > b.seedLength) return 1; else return 0; }; /** * constructor */ SeedMatch() { } /** * set the seed match * * @param queryOffset * @param referenceOffset * @param rank * @return this */ public SeedMatch set(int queryOffset, int referenceOffset, int rank, int seedLength) { this.queryOffset = queryOffset; this.referenceOffset = referenceOffset; this.rank = rank; this.seedLength = seedLength; return this; } public int getRank() { return rank; } public int getQueryOffset() { return queryOffset; } public int getReferenceOffset() { return referenceOffset; } public int getSeedLength() { return seedLength; } public String toString() { return queryOffset + "/" + referenceOffset; } /** * compare first by query position and then by reference position * * @return comparator */ static public Comparator getComparator() { return comparator; } /** * determines whether this seed follows the previous one. It is deemed to follow, if on the same diagonal +-3 * * @param prev * @return true if prev not null and in same frame and similar coordinates */ public boolean follows(SeedMatch prev) { return prev != null && prev.rank == rank && Math.abs((referenceOffset - queryOffset) - (prev.referenceOffset - prev.queryOffset)) < 3; } /** * resize array * * @param array * @return new array */ public static SeedMatch[] resizeAndConstructEntries(SeedMatch[] array, int newSize) { SeedMatch[] result = new SeedMatch[newSize]; if (array == null) { for (int i = 0; i < newSize; i++) result[i] = new SeedMatch(); } else { for (int i = array.length; i < newSize; i++) result[i] = new SeedMatch(); System.arraycopy(array, 0, result, 0, Math.min(newSize, array.length)); } return result; } } malt-0.5.2/src/malt/data/SeedShape.java000066400000000000000000000142041400455127600176210ustar00rootroot00000000000000/* * SeedShape.java Copyright (C) 2020. Daniel H. Huson * * (Some files contain contributions from other authors, who are then mentioned separately.) * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * */ package malt.data; import java.io.IOException; import java.util.LinkedList; import java.util.List; /** * seed shape * Daniel Huson, 8.2014 */ public class SeedShape { private final String shape; private final int[] map; private final int length; private final int weight; private final IAlphabet alphabet; private int jumpToFirstZero = -1; // Source for all seed patterns: Ilie et al. BMC Genomics 2011, 12:280 http://www.biomedcentral.com/1471-2164/12/280 public static final String SINGLE_DNA_SEED = "111110111011110110111111"; public static final String SINGLE_PROTEIN_SEED = "111101101110111"; public static final String[] PROTEIN_SEEDS = new String[]{"111101101110111", "1111000101011001111", "11101001001000100101111", "11101001000010100010100111"}; private int id; // id is 0,..,number of seed shapes-1 /** * constructor * * @param shape * @throws IOException */ public SeedShape(IAlphabet alphabet, String shape) throws IOException { this(alphabet, shape.getBytes()); } /** * constructor * * @param shapeBytes * @throws IOException */ public SeedShape(IAlphabet alphabet, byte[] shapeBytes) throws IOException { this.alphabet = alphabet; StringBuilder buf = new StringBuilder(); for (byte a : shapeBytes) buf.append((char) a); this.shape = buf.toString(); int pos = 0; List list = new LinkedList<>(); for (int i = 0; i < shapeBytes.length; i++) { byte a = shapeBytes[i]; switch (a) { case '0': if (jumpToFirstZero == -1) jumpToFirstZero = i; break; case '1': list.add(pos); break; default: throw new IOException("Illegal character '" + (char) a + "' in shape: " + this.shape); } pos++; } length = shapeBytes.length; weight = list.size(); map = new int[weight]; int i = 0; for (Integer value : list) { map[i++] = value; } // System.err.println("Seed='" + toString()+"', length: " + getMaxIndex() + ", weight: " + getWeight()); // System.err.println("Map: " + Basic.toString(map, ",")); } /** * gets a spaced seed from the given sequence starting at the given offset * * @param sequence * @param offset * @param result if non-null, is used for result * @return spaced seed */ public byte[] getSeed(byte[] sequence, int offset, byte[] result) { if (result == null) result = new byte[weight]; for (int i = 0; i < weight; i++) { result[i] = alphabet.getNormalized(sequence[offset + map[i]]); } // String seq=new String(sequence).substring(offset,offset+length); // System.err.println("Sequence: "+seq+": seed: "+new String(result)); return result; } /** * are query and reference equalOverShorterOfBoth sequences at given offset for the given seed shape? * * @param query * @param qOffset * @param reference * @param rOffset * @return true if equalOverShorterOfBoth for seed shape */ public boolean equalSequences(byte[] query, int qOffset, byte[] reference, int rOffset) { for (int i = 0; i < weight; i++) { if (!alphabet.equal(query[qOffset + map[i]], reference[rOffset + map[i]])) // sequences are normalized, so ok to compare directly return false; } return true; } /** * string representation of shaped seed * * @return string */ public String toString() { return shape; } /** * get bytes * * @return string as bytes */ public byte[] getBytes() { return shape.getBytes(); } /** * length of spaced seed * * @return length */ public int getLength() { return length; } /** * weight of spaced seed * * @return weight */ public int getWeight() { return weight; } /** * create correct size byte array for holding seed results * * @return bytes */ public byte[] createBuffer() { return new byte[getWeight()]; } public IAlphabet getAlphabet() { return alphabet; } /** * compute the number of positions to jump over to get to first 0 * * @return number of ones before first zero */ public int getJumpToFirstZero() { return jumpToFirstZero; } /** * gets the expected number of seeds * * @param numberOfSequences * @param numberOfLetters * @return expected number of seeds */ public long getEstimatedSeedCount(int numberOfSequences, long numberOfLetters, int numberOfJobs) { return Math.max(1, numberOfLetters - numberOfSequences * (weight - 1)) / numberOfJobs; } public void setId(int id) { this.id = id; } public int getId() { return id; } public boolean[] getMask() { boolean[] mask = new boolean[shape.length()]; for (int i = 0; i < shape.length(); i++) if (shape.charAt(i) == '1') mask[i] = true; return mask; } } malt-0.5.2/src/malt/data/SequenceType.java000066400000000000000000000033421400455127600203730ustar00rootroot00000000000000/* * SequenceType.java Copyright (C) 2020. Daniel H. Huson * * (Some files contain contributions from other authors, who are then mentioned separately.) * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * */ package malt.data; /** * sequence type * Daniel Huson, 8.2014 */ public enum SequenceType { DNA, // DNA sequence Protein; // protein sequence /** * get rank * * @param sequenceType * @return rank */ public static int rankOf(SequenceType sequenceType) { for (int i = 0; i < values().length; i++) if (values()[i] == sequenceType) return i; return -1; } /** * get type from rank * * @param rank * @return */ public static SequenceType valueOf(int rank) { return values()[rank]; } /** * get value ignoring case * * @param label * @return value */ public static SequenceType valueOfIgnoreCase(String label) { for (SequenceType type : values()) if (label.equalsIgnoreCase(type.toString())) return type; return null; } } malt-0.5.2/src/malt/data/Translator.java000066400000000000000000000064721400455127600201210ustar00rootroot00000000000000/* * Translator.java Copyright (C) 2020. Daniel H. Huson * * (Some files contain contributions from other authors, who are then mentioned separately.) * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * */ package malt.data; import jloda.util.SequenceUtils; /** * translate DNA sequences into protein sequences * Daniel Huson, 8.2014 */ public class Translator { /** * translate a given DNA sequence into protein sequences * * @param doForward * @param doReverse * @param dnaSequence * @param length * @param frame * @param proteinSequences * @param proteinLengths * @return number of sequences returned */ public static int getBestFrames(boolean doForward, boolean doReverse, byte[] dnaSequence, int length, int[] frame, byte[][] proteinSequences, int[] proteinLengths) { int numberOfResults = 0; for (int shift = 0; shift <= 2; shift++) { if (doForward) { int posProteins = 0; for (int pos = shift; pos < length - 2; pos += 3) { proteinSequences[numberOfResults][posProteins++] = SequenceUtils.getAminoAcid(dnaSequence, pos); } if (isPossibleCodingSequence(proteinSequences[numberOfResults], posProteins)) { proteinLengths[numberOfResults] = posProteins; frame[numberOfResults] = shift + 1; numberOfResults++; } } if (doReverse) { int posProteins = 0; for (int pos = length - 3 - shift; pos >= 0; pos -= 3) { proteinSequences[numberOfResults][posProteins++] = SequenceUtils.getAminoAcidReverse(dnaSequence, pos); } if (isPossibleCodingSequence(proteinSequences[numberOfResults], posProteins)) { proteinLengths[numberOfResults] = posProteins; frame[numberOfResults] = -(shift + 1); numberOfResults++; } } } return numberOfResults; } /** * heuristically determine whether this looks like real coding sequence * * @param sequence * @param length * @return true, if there is a stop-free run of at least 20 amino acids or if whole sequence is stop-free */ private static boolean isPossibleCodingSequence(byte[] sequence, int length) { int nonStopRun = 0; for (int i = 0; i < length; i++) { if (sequence[i] == '*') nonStopRun = 0; else { nonStopRun++; if (nonStopRun == 20) return true; } } return nonStopRun == length; } } malt-0.5.2/src/malt/io/000077500000000000000000000000001400455127600146125ustar00rootroot00000000000000malt-0.5.2/src/malt/io/BlastTextHelper.java000066400000000000000000000106001400455127600205240ustar00rootroot00000000000000/* * BlastTextHelper.java Copyright (C) 2020. Daniel H. Huson * * (Some files contain contributions from other authors, who are then mentioned separately.) * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * */ package malt.io; import jloda.util.BlastMode; /** * some methods to help generate BLAST text output * Daniel Huson, 8.2014 */ public class BlastTextHelper { public static final String FILE_HEADER_BLASTN = "BLASTN output produced by MALT\n\n"; public static final String FILE_HEADER_BLASTX = "BLASTX output produced by MALT\n\n"; public static final String FILE_HEADER_BLASTP = "BLASTP output produced by MALT\n\n"; public static final String FILE_FOOTER_BLAST = "\nEOF\n"; public static final String NO_HITS_STRING = "***** No hits found ******"; public static final byte[] NO_HITS = NO_HITS_STRING.getBytes(); public static final String QUERY_EQUALS_STRING = "\nQuery= "; private static final byte[] QUERY_EQUALS = QUERY_EQUALS_STRING.getBytes(); public static final String QUERY_LETTERS_FORMAT_STRING = "\n (%d letters)\n\n"; public static final String REFERENCE_LENGTH_FORMAT_STRING = "\n Length = %d\n\n"; /** * make the query line * * @return query line */ public static byte[] makeQueryLine(final FastARecord query) { final byte[] header = query.getHeader(); int startOfFirstWord = (header.length > 0 && header[0] == '>' ? 1 : 0); while (startOfFirstWord < header.length && Character.isWhitespace(header[startOfFirstWord])) { startOfFirstWord++; } int endOfFirstWord = startOfFirstWord; while (endOfFirstWord < header.length) { if (Character.isWhitespace(header[endOfFirstWord]) || header[endOfFirstWord] == 0) break; else endOfFirstWord++; } int lengthOfFirstWord = endOfFirstWord - startOfFirstWord; final byte[] result = new byte[QUERY_EQUALS.length + lengthOfFirstWord + 1]; // add one for new-line System.arraycopy(QUERY_EQUALS, 0, result, 0, QUERY_EQUALS.length); System.arraycopy(query.getHeader(), startOfFirstWord, result, QUERY_EQUALS.length, lengthOfFirstWord); result[result.length - 1] = '\n'; return result; } /** * gets the appropriate header line * * @param mode * @return header line */ public static String getBlastTextHeader(BlastMode mode) { switch (mode) { case BlastN: return BlastTextHelper.FILE_HEADER_BLASTN; case BlastX: return BlastTextHelper.FILE_HEADER_BLASTX; case BlastP: return BlastTextHelper.FILE_HEADER_BLASTP; default: return "unknown"; } } /** * get query name followed by tab * * @param query * @return query name plus tab */ public static byte[] getQueryNamePlusTab(final FastARecord query) { final byte[] header = query.getHeader(); int startOfFirstWord = (header.length > 0 && header[0] == '>' ? 1 : 0); while (startOfFirstWord < header.length && Character.isWhitespace(header[startOfFirstWord])) { startOfFirstWord++; } int endOfFirstWord = startOfFirstWord; while (endOfFirstWord < header.length) { if (Character.isWhitespace(header[endOfFirstWord]) || header[endOfFirstWord] == 0) break; else endOfFirstWord++; } int lengthOfFirstWord = endOfFirstWord - startOfFirstWord; byte[] result = new byte[lengthOfFirstWord + 1]; // plus one for tab System.arraycopy(header, startOfFirstWord, result, 0, lengthOfFirstWord); result[lengthOfFirstWord] = '\t'; return result; } } malt-0.5.2/src/malt/io/FastAFileIteratorBytes.java000066400000000000000000000177671400455127600220160ustar00rootroot00000000000000/* * FastAFileIteratorBytes.java Copyright (C) 2020. Daniel H. Huson * * (Some files contain contributions from other authors, who are then mentioned separately.) * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * */ package malt.io; import jloda.util.Basic; import jloda.util.ICloseableIterator; import malt.data.INormalizer; import java.io.BufferedInputStream; import java.io.FileNotFoundException; import java.io.IOException; import java.util.Iterator; /** * Reads in a multifast file and places all headers and sequences in byte arrays * Daniel Huson, 8.2014 */ public class FastAFileIteratorBytes implements Iterator, ICloseableIterator { private final INormalizer normalizer; private byte[] buffer = new byte[10000000]; private int length = 0; private long position = 0; private final long maxProgress; private boolean expectingHeader = true; private final BufferedInputStream inputStream; private boolean isFastQ = false; private boolean ok = true; // haven't seen next() fail yet private boolean nextIsLoaded = false; // have already loaded the next item /** * constructor * * @param fileName * @throws FileNotFoundException */ public FastAFileIteratorBytes(final String fileName, final INormalizer normalizer) throws IOException { this.normalizer = normalizer; inputStream = new BufferedInputStream(Basic.getInputStreamPossiblyZIPorGZIP(fileName), 8192); maxProgress = Basic.guessUncompressedSizeOfFile(fileName); try { int value = inputStream.read(); isFastQ = (value == '@'); } catch (IOException ignored) { } } /** * has next header or sequence * * @return true if has a header or sequence */ public boolean hasNext() { if (!ok) return false; else if (nextIsLoaded) return true; try { if (isFastQ) { // expect four lines per read try { length = 0; if (expectingHeader) { buffer[length++] = (byte) '>'; int value = inputStream.read(); if (value == -1) return ok = false; if (value != '@') buffer[length++] = (byte) value; length = readLineIntoBuffer(inputStream, length); position += length; return ok = (length > 1); } else { length = readLineIntoBuffer(inputStream, length); if (length == 0) return ok = false; position += length; position += skipLine(inputStream); // skip comment line position += skipLine(inputStream); // skip quality line return ok = true; } } catch (IOException e) { return ok = false; } } else { int value; length = 0; boolean first = true; try { while (true) { value = inputStream.read(); if (expectingHeader) { if (value == -1) return ok = false; if (first) { first = false; if (value != '>') buffer[length++] = '>'; } if (value == '\n' || value == '\r') { position += length; return ok = (length > 0); } } else { if (Character.isWhitespace(value)) continue; // skip white space if (value == '>' || value == -1) { position += length; return ok = (length > 0); } } if (length >= buffer.length) growBuffer(); buffer[length++] = (byte) value; } } catch (IOException e) { return ok = false; } } } finally { nextIsLoaded = true; } } /** * get next header or sequence * * @return header or sequence */ public byte[] next() { try { if (!nextIsLoaded && !hasNext()) return null; expectingHeader = !expectingHeader; if (length > 0 || hasNext()) { byte[] result = new byte[length]; if (expectingHeader) // was not expecting header when we entered this method, so this is sequence, so normalize: { for (int i = 0; i < length; i++) result[i] = normalizer.getNormalized(buffer[i]); } else System.arraycopy(buffer, 0, result, 0, length); length = 0; return result; } return null; } finally { nextIsLoaded = false; } } /** * read the next line into the buffer * * @param inputStream * @param offset * @return position of next available position in buffer */ private int readLineIntoBuffer(BufferedInputStream inputStream, int offset) throws IOException { int value = inputStream.read(); while (value != '\r' && value != '\n' && value != -1) { if (offset >= buffer.length) { // need to grow buffer growBuffer(); } buffer[offset++] = (byte) value; value = inputStream.read(); } return offset; } /** * grows the line buffer */ private void growBuffer() { byte[] nextBuffer = new byte[(int) Math.min(Basic.MAX_ARRAY_SIZE, 2L * buffer.length)]; System.arraycopy(buffer, 0, nextBuffer, 0, buffer.length); buffer = nextBuffer; } /** * skip the current line * * @param inputStream * @throws IOException */ private int skipLine(BufferedInputStream inputStream) throws IOException { int skipped = 0; int value = inputStream.read(); while (value != '\r' && value != '\n' && value != -1) { value = inputStream.read(); skipped++; } return skipped; } public void remove() { } /** * close the stream * * @throws IOException */ public void close() throws IOException { inputStream.close(); } /** * gets the maximum progress value * * @return maximum progress value */ public long getMaximumProgress() { return maxProgress; } /** * gets the current progress value * * @return current progress value */ public long getProgress() { return position; } /** * is the file we are reading actually a fastQ file? * * @return true, if fastQ */ public boolean isFastQ() { return isFastQ; } } malt-0.5.2/src/malt/io/FastAReader.java000066400000000000000000000232611400455127600176020ustar00rootroot00000000000000/* * FastAReader.java Copyright (C) 2020. Daniel H. Huson * * (Some files contain contributions from other authors, who are then mentioned separately.) * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * */ package malt.io; import jloda.util.Basic; import jloda.util.ProgressPercentage; import malt.data.DNA5; import malt.data.IAlphabet; import java.io.BufferedInputStream; import java.io.IOException; import java.io.InputStream; import java.util.concurrent.locks.ReentrantLock; /** * Reads in a multifastA (or fastQ) file and places all headers and sequences in byte arrays. In addition, the headers and sequences are 0-terminated * Daniel Huson, 8.2014 */ public class FastAReader { final public static int BUFFER_SIZE = 8192; private final IAlphabet alphabet; private long position = 0; private long maxProgress = 0; private int readCount = 0; private final BufferedInputStream inputStream; private boolean isFastQ = false; private final ProgressPercentage progress; private final ReentrantLock lock = new ReentrantLock(); /** * constructor * * @param fileName * @throws java.io.FileNotFoundException */ public FastAReader(final String fileName, final IAlphabet alphabet) throws IOException { this(fileName, alphabet, null); } /** * constructor * * @param fileName * @param progress * @throws java.io.FileNotFoundException */ public FastAReader(final String fileName, final IAlphabet alphabet, final ProgressPercentage progress) throws IOException { this.alphabet = alphabet; maxProgress = Basic.guessUncompressedSizeOfFile(fileName); this.progress = progress; if (progress != null) { progress.setMaximum(maxProgress); progress.setProgress(0); } // determine file type: { InputStream tmp = new BufferedInputStream(Basic.getInputStreamPossiblyZIPorGZIP(fileName)); int value = tmp.read(); if (value != '@' && value != '>') throw new IOException("Input file '" + fileName + "' does not appear to be in FastA or FastQ format, as it does not start with a '>' or '@'"); isFastQ = (value == '@'); tmp.close(); } inputStream = new BufferedInputStream(Basic.getInputStreamPossiblyZIPorGZIP(fileName), BUFFER_SIZE); } /** * read the next record as fastA. Can be applied to both fastA and fastQ files. * Header and sequence are both 0-terminated. * This method is thread safe * * @param fastARecord * @return true if read * @throws IOException */ public boolean readAsFastA(FastARecord fastARecord) throws IOException { lock.lock(); try { if (isFastQ) { // expect four lines per read readHeader(fastARecord); // read header if (fastARecord.getHeaderLength() == 0) return false; // done fastARecord.getHeader()[0] = '>'; readSequence(fastARecord); if (fastARecord.getSequenceLength() == 0) return false; // done skipLine(); if (fastARecord.isWantQualityValues()) { if (readQualityValues(fastARecord) != fastARecord.sequenceLength) throw new IOException("Error reading quality values: wrong number of bytes"); } else skipLine(); fastARecord.setId(++readCount); return true; } else { // fastA readHeader(fastARecord); // read header byte[] sequence = fastARecord.sequence; // read the sequence, which might be spread over multiple lines int value; int length = 0; while (true) { value = inputStream.read(); position++; if (Character.isWhitespace(value)) continue; // skip white space if (value == '>' || value == -1) { position += length; sequence[length] = 0; fastARecord.sequenceLength = length; if (length == 0) return false; else { fastARecord.setId(++readCount); return true; } } sequence[length++] = alphabet.getNormalized((byte) value); if (length >= sequence.length) sequence = fastARecord.sequence = grow(sequence); } } } finally { if (progress != null) progress.setProgress(position); lock.unlock(); } } /** * reads the header line * * @param fastARecord * @throws IOException */ private void readHeader(FastARecord fastARecord) throws IOException { byte[] aline = fastARecord.header; int value = inputStream.read(); position++; int length = 0; while (value != '\r' && value != '\n' && value != -1) { if (length == 0 && isFastQ) value = '>'; aline[length++] = (byte) value; value = inputStream.read(); position++; if (length >= aline.length) { // need to grow buffer aline = fastARecord.header = grow(aline); } } aline[length] = 0; fastARecord.headerLength = length; } /** * reads the sequence line * * @param fastARecord * @throws IOException */ private void readSequence(FastARecord fastARecord) throws IOException { byte[] aline = fastARecord.sequence; int value = inputStream.read(); position++; int length = 0; while (value != '\r' && value != '\n' && value != -1) { aline[length++] = alphabet.getNormalized((byte) value); value = inputStream.read(); position++; if (length >= aline.length) { // need to grow buffer aline = fastARecord.sequence = grow(aline); } } aline[length] = 0; fastARecord.sequenceLength = length; } /** * reads the quality values line * * @param fastARecord * @return the number of letters read * @throws IOException */ private int readQualityValues(FastARecord fastARecord) throws IOException { byte[] aline = fastARecord.qualityValues; int value = inputStream.read(); position++; int length = 0; while (value != '\r' && value != '\n' && value != -1) { aline[length++] = (byte) value; value = inputStream.read(); position++; if (length >= aline.length) { // need to grow buffer aline = fastARecord.qualityValues = grow(aline); } } aline[length] = 0; return length; } /** * grow the array * * @param bytes * @return bigger copy of array */ private byte[] grow(byte[] bytes) { byte[] result = new byte[Math.min(Integer.MAX_VALUE >> 1, 2 * bytes.length)]; System.arraycopy(bytes, 0, result, 0, bytes.length); return result; } /** * skip the current line * * @throws java.io.IOException */ private void skipLine() throws IOException { int value = inputStream.read(); position++; while (value != '\r' && value != '\n' && value != -1) { value = inputStream.read(); position++; } } /** * close the stream * * @throws java.io.IOException */ public void close() throws IOException { inputStream.close(); if (progress != null) progress.reportTaskCompleted(); } /** * gets the maximum progress value * * @return maximum progress value */ public long getMaximumProgress() { return maxProgress; } /** * gets the current progress value * * @return current progress value */ public long getProgress() { return position; } /** * is the file we are reading actually a fastQ file? * * @return true, if fastQ */ public boolean isFastQ() { return isFastQ; } /** * create a fastA record to be used with this reader * * @param initialLength * @return fastA record */ public static FastARecord createFastARecord(int initialLength, boolean wantQualityValues) { return new FastARecord(initialLength, wantQualityValues); } public static void main(String[] args) throws IOException { FastAReader reader = new FastAReader("/Users/huson/data/ma/input/more.fna", DNA5.getInstance()); FastARecord fastARecord = createFastARecord(1000, false); while (reader.readAsFastA(fastARecord)) { System.err.print(fastARecord); } } } malt-0.5.2/src/malt/io/FastARecord.java000066400000000000000000000061771400455127600176250ustar00rootroot00000000000000/* * FastARecord.java Copyright (C) 2020. Daniel H. Huson * * (Some files contain contributions from other authors, who are then mentioned separately.) * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * */ package malt.io; import jloda.util.Basic; /** * A simple fastA record. Note that header and sequence are 0-terminated * Daniel Huson, 8.2014 */ public class FastARecord { int id; int headerLength; byte[] header; int sequenceLength; byte[] sequence; byte[] qualityValues; /** * constructor */ public FastARecord() { } /** * constructor * * @param initialLength */ public FastARecord(int initialLength, boolean wantQualityValuesIfAvailable) { header = new byte[1000]; sequence = new byte[initialLength]; if (wantQualityValuesIfAvailable) qualityValues = new byte[initialLength]; } public int getId() { return id; } public void setId(int id) { this.id = id; } /** * get header length * * @return length */ public int getHeaderLength() { return headerLength; } /** * get the header * * @return header (0-terminated) */ public byte[] getHeader() { return header; } public String getHeaderString() { return Basic.toString(header, headerLength); } /** * get the sequence length * * @return length */ public int getSequenceLength() { return sequenceLength; } /** * get the sequence * * @return sequence (0-terminated) */ public byte[] getSequence() { return sequence; } /** * set the sequence * * @param sequence * @param length */ public void setSequence(byte[] sequence, int length) { this.sequence = sequence; this.sequence[length] = 0; } public String getSequenceString() { return Basic.toString(sequence, sequenceLength); } public byte[] getQualityValues() { return qualityValues; } public String getQualityValuesString() { return Basic.toString(qualityValues, sequenceLength); } /** * get as string * * @return */ public String toString() { return (new StringBuilder()).append(Basic.toString(header, headerLength)).append("\n").append(Basic.toString(sequence, sequenceLength)).append("\n").toString(); } public boolean isWantQualityValues() { return qualityValues != null; } } malt-0.5.2/src/malt/io/FileWriterRanked.java000066400000000000000000000215171400455127600206640ustar00rootroot00000000000000/* * FileWriterRanked.java Copyright (C) 2020. Daniel H. Huson * * (Some files contain contributions from other authors, who are then mentioned separately.) * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * */ package malt.io; import jloda.util.Basic; import java.io.*; import java.util.concurrent.ArrayBlockingQueue; import java.util.concurrent.CountDownLatch; /** * writes byte strings to a file in increasing order of rank * Daniel Huson, 8.2014 */ public class FileWriterRanked { final public static int QUEUE_LENGTH = 1000000; private final static OutputItem SENTINEL = new OutputItem(0, null); private final ArrayBlockingQueue outputQueue; private final ArrayBlockingQueue[] threadSpecificWaitQueues; private final Writer writer; private final boolean isFile; private final StringBuilder fileFooter; private long nextRank; private boolean isClosing = false; private final CountDownLatch hasFinishedOutput = new CountDownLatch(1); private int queueHighWaterMark = 0; /** * constructor * * @param fileName * @param smallestRank value of first byte string to be written * @throws java.io.IOException */ public FileWriterRanked(String fileName, final int numberOfThreads, int smallestRank) throws IOException { // one wait queue for each thread: threadSpecificWaitQueues = new ArrayBlockingQueue[numberOfThreads]; for (int i = 0; i < threadSpecificWaitQueues.length; i++) threadSpecificWaitQueues[i] = new ArrayBlockingQueue<>(QUEUE_LENGTH); // the output queue: outputQueue = new ArrayBlockingQueue<>(QUEUE_LENGTH); // the output writer: OutputStream outs; if (fileName == null || fileName.equalsIgnoreCase("stdout")) { isFile = false; outs = System.out; } else { isFile = true; outs = Basic.getOutputStreamPossiblyZIPorGZIP(fileName); } writer = new BufferedWriter(new OutputStreamWriter(outs), 10 * 1024 * 1024); // ten megabyte buffer, not sure whether this makes a difference fileFooter = new StringBuilder(); nextRank = smallestRank; // this thread collects output items in order from thread-specific waiting queues and places them on the output queue final Thread thread1 = new Thread(() -> { try { while (true) { boolean allEmpty = true; for (ArrayBlockingQueue queue : threadSpecificWaitQueues) { OutputItem item = queue.peek(); while (item != null && item.rank == nextRank) { if (allEmpty) { allEmpty = false; if (queue.size() > queueHighWaterMark) queueHighWaterMark = queue.size(); } try { outputQueue.put(item); } catch (InterruptedException e) { Basic.caught(e); } nextRank++; item = queue.poll(); // don't use take(), don't want to block here... } } if (allEmpty) { if (isClosing) { outputQueue.put(SENTINEL); return; } else try { Thread.sleep(1); } catch (InterruptedException e) { Basic.caught(e); } } } } catch (InterruptedException ex) { Basic.caught(ex); } }); thread1.start(); // this thread writes output to file final Thread thread2 = new Thread(() -> { try { while (true) { OutputItem item = outputQueue.take(); if (item == SENTINEL) { hasFinishedOutput.countDown(); return; } byte[][] strings = item.strings; if (strings != null) { for (byte[] string : strings) { byte b = 0; for (byte aString : string) { b = aString; if (b == 0) break; // zero-terminated byte string writer.write((char) b); } if (b != '\t') // if this ends on a tab, don't add new line, it is the query-name for BlastTab or SAM writer.write('\n'); } } } } catch (Exception ex) { Basic.caught(ex); } }); thread2.start(); } /** * close * * @throws java.io.IOException */ public void close() throws IOException { isClosing = true; try { hasFinishedOutput.await(); } catch (InterruptedException e) { Basic.caught(e); } if (fileFooter.length() > 0) writer.write(fileFooter.toString()); writer.flush(); if (isFile) writer.close(); /* if (queueHighWaterMark > 1) { System.err.println("(outputQueueHighWaterMark: " + queueHighWaterMark+")"); } */ } /** * Write byte strings to the out stream by rank. * By rank means that output is generated only when all output of lower output * has already been written * Does not make a copy of the byte arrays, so shouldn't recycle because unclear when this will be written * Then must not be overwritten * * @param rank each call must have a different rank and no rank can be skipped * @param strings can be null */ public void writeByRank(int threadId, long rank, byte[][] strings) { try { threadSpecificWaitQueues[threadId].put(new OutputItem(rank, strings)); } catch (InterruptedException e) { Basic.caught(e); } } /** * write a header and body by rank. By rank means that output is generated only when all output of lower output * has already been written * Does not make a copy of the byte arrays, so shouldn't recycle because unclear when this will be written * * @param rank * @param header * @param body */ public void writeByRank(int threadId, long rank, byte[] header, byte[] body) { try { threadSpecificWaitQueues[threadId].put(new OutputItem(rank, new byte[][]{header, body})); } catch (InterruptedException e) { Basic.caught(e); } } /** * skip a rank * * @param rank */ public void skipByRank(int threadId, int rank) { try { threadSpecificWaitQueues[threadId].put(new OutputItem(rank, null)); } catch (InterruptedException e) { Basic.caught(e); } } /** * write this at the top of the file * * @param string * @throws java.io.IOException */ public void writeFirst(String string) throws IOException { writer.write(string); } /** * write this at the end of the file * * @param string * @throws java.io.IOException */ public void writeLast(String string) { fileFooter.append(string); } } /** * output item consists of rank and bytes to write */ class OutputItem { final long rank; final byte[][] strings; OutputItem(long rank, byte[][] strings) { this.rank = rank; this.strings = strings; } public String toString() { StringBuilder buf = new StringBuilder(); buf.append("rank=").append(this.rank); if (strings != null) { for (byte[] string : strings) buf.append(Basic.toString(string)); } return buf.toString(); } } malt-0.5.2/src/malt/io/RMA6Writer.java000066400000000000000000000272631400455127600173710ustar00rootroot00000000000000/* * RMA6Writer.java Copyright (C) 2020. Daniel H. Huson * * (Some files contain contributions from other authors, who are then mentioned separately.) * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * */ package malt.io; import jloda.util.Basic; import jloda.util.CanceledException; import jloda.util.ProgressPercentage; import malt.MaltOptions; import malt.Version; import malt.data.ReadMatch; import malt.mapping.Mapping; import malt.mapping.MappingManager; import megan.classification.Classification; import megan.core.ContaminantManager; import megan.core.Document; import megan.core.SyncArchiveAndDataTable; import megan.data.IReadBlock; import megan.data.IReadBlockIterator; import megan.io.InputOutputReaderWriter; import megan.rma6.MatchLineRMA6; import megan.rma6.RMA6Connector; import megan.rma6.RMA6FileCreator; import java.io.IOException; import java.util.Arrays; /** * Create an RMA6 file from SAM data in Malt *

* Daniel Huson, 6.2015 */ public class RMA6Writer { private final RMA6FileCreator rma6FileCreator; private final String rma6File; private final boolean parseHeaders; private final String[] cNames; private final int maxMatchesPerQuery; private final MaltOptions maltOptions; private final MatchLineRMA6[] matches; private final int[][] match2classification2id; private byte[] queryText = new byte[10000]; private byte[] matchesText = new byte[10000]; /** * constructor * * @param maltOptions * @param rma6File * @throws IOException */ public RMA6Writer(final MaltOptions maltOptions, String rma6File) throws IOException { System.err.println("Starting file: " + rma6File); this.maltOptions = maltOptions; this.rma6File = rma6File; this.parseHeaders = maltOptions.isParseHeaders(); maxMatchesPerQuery = maltOptions.getMaxAlignmentsPerQuery(); cNames = MappingManager.getCNames(); int taxonMapperIndex = Basic.getIndex(Classification.Taxonomy, Arrays.asList(cNames)); matches = new MatchLineRMA6[maxMatchesPerQuery]; for (int i = 0; i < matches.length; i++) { matches[i] = new MatchLineRMA6(cNames.length, taxonMapperIndex); } match2classification2id = new int[maxMatchesPerQuery][cNames.length]; rma6FileCreator = new RMA6FileCreator(rma6File, true); rma6FileCreator.writeHeader(Version.SHORT_DESCRIPTION, maltOptions.getMode(), cNames, false); rma6FileCreator.startAddingQueries(); } /** * process the matches associated with a given query. * This is used in malt1 * * @param queryHeader * @param matchesArray * @param numberOfMatches * @throws IOException */ public synchronized void processMatches(String queryHeader, String querySequence, ReadMatch[] matchesArray, int numberOfMatches) throws IOException { // setup query text: byte[] queryName = Basic.swallowLeadingGreaterSign(Basic.getFirstWord(queryHeader)).getBytes(); byte[] queryHeaderText = queryHeader.getBytes(); byte[] querySequenceText = querySequence.getBytes(); if (queryHeaderText.length + querySequenceText.length + 100 > queryText.length) { queryText = new byte[100 + queryHeaderText.length + querySequenceText.length]; } System.arraycopy(queryHeaderText, 0, queryText, 0, queryHeaderText.length); int queryTextLength = queryHeaderText.length; queryText[queryTextLength++] = '\n'; System.arraycopy(querySequenceText, 0, queryText, queryTextLength, querySequenceText.length); queryTextLength += querySequenceText.length; queryText[queryTextLength++] = '\n'; final String[] key = new String[cNames.length]; for (int i = 0; i < cNames.length; i++) { key[i] = getKey(cNames[i]); } // setup matches text: int matchesTextLength = 0; numberOfMatches = Math.min(maxMatchesPerQuery, numberOfMatches); for (int m = 0; m < numberOfMatches; m++) { final ReadMatch match = matchesArray[m]; final byte[] matchText = match.getRMA6Text(); final int approximateLengthToAdd = matchesTextLength + matchText.length + queryName.length; if (approximateLengthToAdd + 100 > matchesText.length) { byte[] tmp = new byte[approximateLengthToAdd + 10000]; System.arraycopy(matchesText, 0, tmp, 0, matchesTextLength); matchesText = tmp; } System.arraycopy(queryName, 0, matchesText, matchesTextLength, queryName.length); matchesTextLength += queryName.length; matchesText[matchesTextLength++] = '\t'; System.arraycopy(matchText, 0, matchesText, matchesTextLength, matchText.length); matchesTextLength += matchText.length; matchesText[matchesTextLength++] = '\n'; matches[m].setBitScore(match.getBitScore()); matches[m].setExpected(match.getExpected()); matches[m].setPercentIdentity(match.getPercentIdentity()); final String refHeader = (parseHeaders ? getWordAsString(match.getRMA6Text(), 2) : null); for (int i = 0; i < cNames.length; i++) { int id = 0; if (parseHeaders) id = parseIdInHeader(key[i], refHeader); if (id == 0) { Mapping mapping = MappingManager.getMapping(i); if (mapping != null) id = MappingManager.getMapping(i).get(match.getReferenceId()); } match2classification2id[m][i] = id; matches[m].setFId(i, id); } } rma6FileCreator.addQuery(queryText, queryTextLength, numberOfMatches, matchesText, matchesTextLength, match2classification2id, 0); } private int parseIdInHeader(String key, String word) { int pos = word.indexOf(key); if (pos != -1) { if (Basic.isInteger(word.substring(pos + key.length()))) return Basic.parseInt(word.substring(pos + key.length())); } return 0; } /** * finish generation of RMA6 file * * @throws IOException * @throws CanceledException */ public void close(String contaminantsFile) throws IOException { try { System.err.println("Finishing file: " + rma6File); rma6FileCreator.endAddingQueries(); rma6FileCreator.writeClassifications(new String[0], null, null); rma6FileCreator.close(); final boolean pairedReads = maltOptions.isPairedReads(); if (pairedReads) { // update paired reads info and then run dataprocessor long count = 0; try (InputOutputReaderWriter raf = new InputOutputReaderWriter(rma6File, "rw"); IReadBlockIterator it = (new RMA6Connector(rma6File)).getAllReadsIterator(0, 1000, false, false)) { final ProgressPercentage progress = new ProgressPercentage("Linking paired reads"); progress.setProgress(0); progress.setProgress(it.getMaximumProgress()); while (it.hasNext()) { final IReadBlock readBlock = it.next(); if (readBlock.getMateUId() > 0) { if (readBlock.getMateUId() > readBlock.getUId()) throw new IOException("Mate uid=" + readBlock.getMateUId() + ": too big"); raf.seek(readBlock.getMateUId()); raf.writeLong(readBlock.getUId()); count++; } progress.setProgress(it.getProgress()); } progress.close(); System.err.printf("Number of pairs:%,14d%n", count); } } // we need to run data processor final Document doc = new Document(); doc.setTopPercent(maltOptions.getTopPercentLCA()); doc.setLcaAlgorithm(maltOptions.isUseWeightedLCA() ? Document.LCAAlgorithm.weighted : Document.LCAAlgorithm.naive); doc.setLcaCoveragePercent(maltOptions.getLcaCoveragePercent()); doc.setMinSupportPercent(maltOptions.getMinSupportPercentLCA()); doc.setMinSupport(maltOptions.getMinSupportLCA()); doc.setMaxExpected((float) maltOptions.getMaxExpected()); doc.setMinScore((float) maltOptions.getMinBitScore()); doc.setPairedReads(pairedReads); doc.setMaxExpected((float) maltOptions.getMaxExpected()); doc.setMinPercentIdentity(maltOptions.getMinPercentIdentityLCA()); doc.setUseIdentityFilter(maltOptions.isUsePercentIdentityFilterLCA()); doc.getActiveViewers().addAll(Arrays.asList(MappingManager.getCNames())); doc.setReadAssignmentMode(Document.ReadAssignmentMode.readCount); // todo: make this an option if (Basic.fileExistsAndIsNonEmpty(contaminantsFile)) { ContaminantManager contaminantManager = new ContaminantManager(); contaminantManager.read(contaminantsFile); doc.getDataTable().setContaminants(contaminantManager.getTaxonIdsString()); } doc.getMeganFile().setFileFromExistingFile(rma6File, false); doc.loadMeganFile(); doc.processReadHits(); // update and then save auxiliary data: final String sampleName = Basic.replaceFileSuffix(Basic.getFileNameWithoutPath(rma6File), ""); SyncArchiveAndDataTable.syncRecomputedArchive2Summary(doc.getReadAssignmentMode(), sampleName, "LCA", doc.getBlastMode(), doc.getParameterString(), new RMA6Connector(rma6File), doc.getDataTable(), 0); doc.saveAuxiliaryData(); } catch (CanceledException ex) { throw new IOException(ex); // this can't happen because ProgressPercent never throws CanceledException } } /** * get key * * @param fName * @return key */ private static String getKey(String fName) { switch (fName.toLowerCase()) { case "interpro2go": return "ipr|"; case "eggnog": return "cog|"; default: return fName.toLowerCase() + "|"; } } /** * get a word as string * * @param text * @param whichWord * @return string or null */ private static String getWordAsString(byte[] text, int whichWord) { int start = -1; whichWord--; for (int i = 0; i < text.length; i++) { if (Character.isWhitespace(text[i])) { if (whichWord > 0) { whichWord--; if (whichWord == 0) start = i; } else if (whichWord == 0) { return new String(text, start, i - start); } } } if (start >= 0) return new String(text, start, text.length - start); return null; } } malt-0.5.2/src/malt/io/SAMHelper.java000066400000000000000000000410541400455127600172410ustar00rootroot00000000000000/* * SAMHelper.java Copyright (C) 2020. Daniel H. Huson * * (Some files contain contributions from other authors, who are then mentioned separately.) * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * */ package malt.io; import jloda.util.BlastMode; import malt.data.DNA5; /** * helps to create a SAM line from an alignment * Daniel Huson, 8.2014 */ public class SAMHelper { private static final String FILE_HEADER_BLASTN_TEMPLATE = "@HD\tVN:1.5\tSO:unsorted\tGO:query\n@PG\tID:1\tPN:MALT\tCL:%s\tDS:BlastN\n@RG\tID:1\tPL:unknown\tSM:unknown\n@CO\tBlastN-like alignments\n" + "@CO\tReporting AS: bitScore, ZR: rawScore, ZE: expected, ZI: percent identity, ZL: reference length\n"; private static final String FILE_HEADER_BLASTP_TEMPLATE = "@HD\tVN:1.5\tSO:unsorted\tGO:query\n@PG\tID:1\tPN:MALT\tCL:%s\tDS:BlastP\n@RG\tID:1\tPL:unknown\tSM:unknown\n@CO\tBlastP-like alignments\n" + "@CO\tReporting AS: bitScore, ZR: rawScore, ZE: expected, ZI: percent identity, ZL: reference length\n"; private static final String FILE_HEADER_BLASTX_TEMPLATE = "@HD\tVN:1.5\tSO:unsorted\tGO:query\n@PG\tID:1\tPN:MALT\tCL:%s\tDS:BlastX\n@RG\tID:1\tPL:unknown\tSM:unknown\n@CO\tBlastX-like alignments\n" + "@CO\tReporting AS: bitScore, ZR: rawScore, ZE: expected, ZI: percent identity, ZL: reference length, ZF: frame, ZS: query start DNA coordinate\n"; /* 0 QNAME String 1 FLAG Int 2 RNAME String 3 POS Int 4 MAPQ Int 5 CIGAR String 6 RNEXT String 7 PNEXT Int 8 TLEN Int 9 SEQ String 10 QUAL String Regexp/Range [!-?A-~]{1,255} [0,216 -1] \*|[!-()+-<>-~][!-~]* [0,229 -1][0,28 -1] \*|([0-9]+[MIDNSHPX=])+ \*|=|[!-()+-<>-~][!-~]* [0,229 -1] [-229 +1,229 -1] \*|[A-Za-z=.]+ [!-~]+ 11 additional stuff including score and MD */ /** * creates a SAM line. If queryHeader==null, does not output the initial query token * * @param mode * @param queryHeader * @param queryStart * @param queryStartBlastX * @param queryEnd * @param queryLength * @param alignedQuery * @param referenceHeader * @param referenceStart * @param referenceEnd * @param alignedReference * @param referenceLength * @param bitScore * @param rawScore * @param expected * @param percentIdentity * @param frame * @param softClipped * @return */ public static String createSAMLine(final BlastMode mode, final byte[] queryHeader, final byte[] querySequence, final int queryStart, final int queryStartBlastX, final int queryEnd, final int queryLength, final byte[] alignedQuery, final byte[] referenceHeader, final int referenceStart, final int referenceEnd, final byte[] alignedReference, final int referenceLength, final double bitScore, final int rawScore, final double expected, final float percentIdentity, int frame, final byte[] qualityValues, boolean softClipped) { if (querySequence == null && softClipped) softClipped = false; final StringBuilder buffer = new StringBuilder(); // QNAME: boolean first = true; if (queryHeader != null) { for (byte a : queryHeader) { if (first && a == '>') { first = false; continue; } if (a == 0 || Character.isSpaceChar(a)) break; buffer.append((char) a); } buffer.append('\t'); } // FLAG final boolean reverseComplemented = ((queryStart < queryEnd) != (referenceStart < referenceEnd)); final int queryOffset; switch (mode) { case BlastN: if (reverseComplemented) { queryOffset = queryLength - queryEnd; buffer.append(0x10); // SEQ is reverse complemented } else { queryOffset = queryStart; buffer.append(0); } break; case BlastX: if (reverseComplemented) buffer.append(0x10); // SEQ is reverse complemented else buffer.append(0); queryOffset = 0; // will explicitly save query start and query end break; default: case BlastP: queryOffset = queryStart; buffer.append(0); } buffer.append('\t'); // RNAME: first = true; for (byte a : referenceHeader) { if (first && a == '>') { first = false; continue; } if (a == 0 || Character.isSpaceChar(a)) break; buffer.append((char) a); } buffer.append('\t'); // POS: int pos = Math.min(referenceStart, referenceEnd); buffer.append(pos); buffer.append('\t'); // MAPQ buffer.append("255"); // unknown buffer.append('\t'); // CIGAR appendCigar(alignedQuery, queryOffset, queryLength, alignedReference, reverseComplemented, softClipped, buffer); buffer.append('\t'); // RNEXT buffer.append("*"); // unknown buffer.append('\t'); // PNEXT buffer.append("0"); // unknown buffer.append('\t'); // TLEN buffer.append("0"); buffer.append('\t'); // SEQ if (softClipped && querySequence != null) { if (reverseComplemented) { for (int i = queryLength - 1; i >= 0; i--) { buffer.append((char) DNA5.getInstance().getBaseComplement(querySequence[i])); } } else { for (int i = 0; i < queryLength; i++) buffer.append((char) querySequence[i]); } } else { if (reverseComplemented) { for (int i = alignedQuery.length - 1; i >= 0; i--) { byte a = alignedQuery[i]; if (a != '-') buffer.append((char) DNA5.getInstance().getBaseComplement(a)); } } else { for (byte a : alignedQuery) { if (a != '-') buffer.append((char) a); } } } buffer.append('\t'); // QUAL if (qualityValues == null) buffer.append("*"); else { if (softClipped) { if (reverseComplemented) { for (int i = queryLength - 1; i >= 0; i--) buffer.append((char) qualityValues[i]); } else { for (int i = 0; i < queryLength; i++) buffer.append((char) qualityValues[i]); } } else { if (reverseComplemented) { for (int i = queryStart; i < queryEnd; i++) buffer.append((char) qualityValues[queryLength - (i + 1)]); } else { for (int i = queryStart; i < queryEnd; i++) buffer.append((char) qualityValues[i]); } } } buffer.append('\t'); // optional stuff: buffer.append(String.format("AS:i:%d\t", (int) Math.round(bitScore))); buffer.append(String.format("NM:i:%d\t", computeEditDistance(alignedQuery, alignedReference))); buffer.append(String.format("ZL:i:%d\t", referenceLength)); buffer.append(String.format("ZR:i:%d\t", rawScore)); buffer.append(String.format("ZE:f:%g\t", (float) expected)); buffer.append(String.format("ZI:i:%d\t", (int) Math.round(percentIdentity))); if (mode == BlastMode.BlastX) { buffer.append(String.format("ZF:i:%d\t", frame)); buffer.append(String.format("ZS:i:%d\t", queryStartBlastX)); } appendMDString(alignedQuery, alignedReference, reverseComplemented, buffer); return buffer.toString(); } /** * append the cigar string * * @param alignedQuery * @param queryOffset * @param queryLength * @param alignedReference * @param reverseComplemented * @param softClipped * @param buffer */ private static void appendCigar(byte[] alignedQuery, int queryOffset, int queryLength, byte[] alignedReference, boolean reverseComplemented, boolean softClipped, StringBuilder buffer) { int clip = (!reverseComplemented ? queryOffset : (queryLength - queryOffset - alignedQuery.length)); if (clip > 0) { buffer.append(clip).append(softClipped ? "S" : "H"); } if (reverseComplemented) { char state = 'M'; // M in match, I insert, D deletion int count = 0; for (int i = alignedQuery.length - 1; i >= 0; i--) { if (alignedQuery[i] == '-') { if (state == 'D') { count++; } else if (count > 0) { buffer.append(count).append(state); state = 'D'; count = 1; } } else if (alignedReference[i] == '-') { if (state == 'I') { count++; } else if (count > 0) { buffer.append(count).append(state); state = 'I'; count = 1; } } else { // match or mismatch if (state == 'M') { count++; } else if (count > 0) { buffer.append(count).append(state); state = 'M'; count = 1; } } } if (count > 0) { buffer.append(count).append(state); } } else { char cigarState = 'M'; // M in match, D deletion, I insertion int count = 0; for (int i = 0; i < alignedQuery.length; i++) { if (alignedQuery[i] == '-') { if (cigarState == 'D') { count++; } else if (count > 0) { buffer.append(count).append(cigarState); cigarState = 'D'; count = 1; } } else if (alignedReference[i] == '-') { if (cigarState == 'I') { count++; } else if (count > 0) { buffer.append(count).append(cigarState); cigarState = 'I'; count = 1; } } else { // match or mismatch if (cigarState == 'M') { count++; } else if (count > 0) { buffer.append(count).append(cigarState); cigarState = 'M'; count = 1; } } } if (count > 0) { buffer.append(count).append(cigarState); } } clip = (reverseComplemented ? queryOffset : (queryLength - queryOffset - alignedQuery.length)); if (clip > 0) { buffer.append(clip).append(softClipped ? "S" : "H"); } } /** * append the MD string * * @param alignedQuery * @param alignedReference * @param reverseComplemented * @param buffer */ private static void appendMDString(final byte[] alignedQuery, final byte[] alignedReference, final boolean reverseComplemented, final StringBuilder buffer) { buffer.append("MD:Z:"); if (reverseComplemented) { int countMatches = 0; boolean inDeletion = false; for (int i = alignedQuery.length - 1; i >= 0; i--) { if (alignedQuery[i] == '-') { // gap in query if (countMatches > 0) { buffer.append(countMatches); countMatches = 0; } if (!inDeletion) { buffer.append("^"); inDeletion = true; } buffer.append((char) (DNA5.getInstance().getBaseComplement(alignedReference[i]))); } else if (alignedReference[i] != '-') { // match or mismatch if (alignedQuery[i] == alignedReference[i]) { countMatches++; } else { if (inDeletion) buffer.append(0); if (countMatches > 0) { buffer.append(countMatches); countMatches = 0; } buffer.append((char) (DNA5.getInstance().getBaseComplement(alignedReference[i]))); } if (inDeletion) inDeletion = false; } // else alignedReference[i] == '-': this has no effect } if (countMatches > 0) buffer.append(countMatches); else if (inDeletion) buffer.append(0); } else { int countMatches = 0; boolean inDeletion = false; for (int i = 0; i < alignedQuery.length; i++) { if (alignedQuery[i] == '-') { // gap in query if (countMatches > 0) { buffer.append(countMatches); countMatches = 0; } if (!inDeletion) { buffer.append("^"); inDeletion = true; } buffer.append((char) alignedReference[i]); } else if (alignedReference[i] != '-') { // match or mismatch if (alignedQuery[i] == alignedReference[i]) { countMatches++; } else { if (inDeletion) buffer.append("0"); if (countMatches > 0) { buffer.append(countMatches); countMatches = 0; } buffer.append((char) alignedReference[i]); } if (inDeletion) inDeletion = false; } // else alignedReference[i] == '-': this has no effect } if (countMatches > 0) buffer.append(countMatches); else if (inDeletion) buffer.append(0); } } /** * compute edit distance from alignment * * @param alignedQuery * @param alignedReference * @return edit distance */ private static int computeEditDistance(byte[] alignedQuery, byte[] alignedReference) { int distance = 0; for (int i = 0; i < alignedQuery.length; i++) { if (alignedQuery[i] == '-' || alignedReference[i] == '-' || alignedQuery[i] != alignedReference[i]) distance++; } return distance; } /** * gets the SAM header line * * @param mode * @return SAM header line or null */ public static String getSAMHeader(BlastMode mode, String commandLine) { switch (mode) { case BlastN: return String.format(FILE_HEADER_BLASTN_TEMPLATE, (commandLine != null ? commandLine : "")); case BlastP: return String.format(FILE_HEADER_BLASTP_TEMPLATE, (commandLine != null ? commandLine : "")); case BlastX: return String.format(FILE_HEADER_BLASTX_TEMPLATE, (commandLine != null ? commandLine : "")); default: return "???"; } } } malt-0.5.2/src/malt/mapping/000077500000000000000000000000001400455127600156365ustar00rootroot00000000000000malt-0.5.2/src/malt/mapping/Mapping.java000066400000000000000000000162741400455127600201060ustar00rootroot00000000000000/* * Mapping.java Copyright (C) 2020. Daniel H. Huson * * (Some files contain contributions from other authors, who are then mentioned separately.) * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * */ package malt.mapping; import jloda.util.Basic; import jloda.util.CanceledException; import jloda.util.ProgressListener; import jloda.util.ProgressPercentage; import malt.data.ISequenceAccessor; import malt.data.RefIndex2ClassId; import megan.accessiondb.AccessAccessionMappingDatabase; import megan.classification.Classification; import megan.classification.IdParser; import java.io.File; import java.io.IOException; import java.sql.SQLException; import java.util.Collection; import java.util.HashMap; import java.util.Map; /** * Maintains mapping from Reference indices to classification * Daniel Huson, 2.2016 */ public class Mapping extends RefIndex2ClassId { private final static String version = "V1.1"; private final String fName; /** * construct a table * * @param maxIndex */ public Mapping(String fName, int maxIndex) { super(maxIndex); this.fName = fName; } /** * compute the mapping for the given reference database * * @param referencesDB * @param progress */ public static Mapping create(String fName, ISequenceAccessor referencesDB, IdParser classificationMapper, ProgressListener progress) throws IOException { final Mapping mapping = new Mapping(fName, referencesDB.getNumberOfSequences()); final String tag = Classification.createShortTag(fName); progress.setMaximum(referencesDB.getNumberOfSequences()); progress.setProgress(0); for (int i = 0; i < referencesDB.getNumberOfSequences(); i++) { String header = Basic.toString(referencesDB.getHeader(i)); int classId = classificationMapper.getIdFromHeaderLine(header); if (classId != 0) { mapping.put(i, classId); referencesDB.extendHeader(i, tag, classId); } progress.incrementProgress(); } if (progress instanceof ProgressPercentage) progress.close(); return mapping; } /** * compute the mapping for the given reference database * * @param referencesDB * @param progress */ public static Map create(Collection namesToUse, ISequenceAccessor referencesDB, AccessAccessionMappingDatabase mappingDatabase, ProgressListener progress) throws IOException, SQLException { final Collection cNames = mappingDatabase.getClassificationNames(); final int maxIndex = cNames.stream().mapToInt(name -> { try { return mappingDatabase.getClassificationIndex(name); } catch (SQLException throwables) { throwables.printStackTrace(); return 0; } }).max().orElse(-1); final Map mappings = new HashMap<>(); final String[] cIndex2Name = new String[maxIndex + 1]; final String[] tags = new String[maxIndex + 1]; { int c = 0; for (String cName : cNames) { final int index = mappingDatabase.getClassificationIndex(cName) - 2; if (namesToUse.contains(cName)) { mappings.put(cName, new Mapping(cName, referencesDB.getNumberOfSequences())); cIndex2Name[index] = cName; tags[c] = Classification.createShortTag(cName); } c++; } } progress.setMaximum(referencesDB.getNumberOfSequences()); progress.setProgress(0); final int chunkSize = 10000; final String[] accessions = new String[chunkSize]; for (int offset = 0; offset < referencesDB.getNumberOfSequences(); offset += chunkSize) { final int numberInChunk = Math.min(chunkSize, referencesDB.getNumberOfSequences() - offset * chunkSize); for (int r = 0; r < numberInChunk; r++) { accessions[r] = getFirstWordAccession(referencesDB.getHeader(offset + r)); } final Map accession2ids = mappingDatabase.getValues(accessions, numberInChunk); for (int r = 0; r < numberInChunk; r++) { if (accessions[r].length() > 0) { final int[] ids = accession2ids.get(accessions[r]); if (ids != null) { //System.err.println((offset+r)+" -> "+Basic.toString(referencesDB.getHeader(offset + r))+" -> "+accessions[r]); for (int c = 0; c < cIndex2Name.length; c++) { if (cIndex2Name[c] != null) { final int index = ids[c]; if (index != 0) { //System.err.println(cIndex2Name[c]+" -> "+index); mappings.get(cIndex2Name[c]).put(offset + r, index); referencesDB.extendHeader(c, tags[c], index); } } } } } } progress.setProgress(offset + numberInChunk); } return mappings; } public static String getFirstWordAccession(byte[] bytes) { final String aLine = Basic.toString(bytes); int a = 0; while (a < aLine.length()) { if (aLine.charAt(a) == '>' || aLine.charAt(a) == '@' || Character.isWhitespace(aLine.charAt(a))) a++; else break; } int b = a + 1; while (b < aLine.length()) { if (Character.isLetterOrDigit(aLine.charAt(b)) || aLine.charAt(b) == '_') b++; else break; } if (b - a > 4) { return aLine.substring(a, b); } else return ""; } /** * save to a stream and then close the stream * * @param file * @throws IOException */ public void save(File file) throws IOException { super.save(file, makeMagicNumber(fName)); } /** * construct from an existing file * * @param file * @throws IOException * @throws CanceledException */ public Mapping(String fName, File file) throws IOException, CanceledException { super(file, makeMagicNumber(fName)); this.fName = fName; } private static byte[] makeMagicNumber(String fName) { return ("MA" + fName + version).getBytes(); } } malt-0.5.2/src/malt/mapping/MappingManager.java000066400000000000000000000072641400455127600214000ustar00rootroot00000000000000/* * MappingManager.java Copyright (C) 2020. Daniel H. Huson * * (Some files contain contributions from other authors, who are then mentioned separately.) * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * */ package malt.mapping; import jloda.util.Basic; import jloda.util.CanceledException; import megan.classification.Classification; import megan.classification.ClassificationManager; import java.io.File; import java.io.IOException; import java.util.ArrayList; /** * manages MALT mapping files * Daniel Huson, 2.2016 */ public class MappingManager { private static String[] cNames; private static int taxonomyIndex; private static Mapping[] mappings; /** * load all mappings * * @param cNames * @param indexDirectory * @throws IOException * @throws CanceledException */ public static void loadMappings(String[] cNames, String indexDirectory) throws IOException, CanceledException { MappingManager.cNames = cNames; mappings = new Mapping[cNames.length]; taxonomyIndex = -1; for (int i = 0; i < cNames.length; i++) { String cName = cNames[i]; if (cName.equals(Classification.Taxonomy)) taxonomyIndex = i; String fileName = cName.toLowerCase() + ".idx"; ClassificationManager.ensureTreeIsLoaded(cName); final File file = new File(indexDirectory, fileName); if (file.exists()) mappings[i] = new Mapping(cName, file); else mappings[i] = null; } } /** * get all names of loaded mappings * * @return names */ public static String[] getCNames() { return cNames; } /** * gets the appopriate mapping for the given fID * * @param fID * @return mapping */ public static Mapping getMapping(int fID) { return mappings[fID]; } /** * gets the taxonomy mapping * * @return taxonomy mapping */ public static Mapping getTaxonomyMapping() { if (taxonomyIndex >= 0) return getMapping(taxonomyIndex); else return null; } /** * determine all available classifications * * @param indexDirectory * @return list of available classifications */ public static String[] determineAvailableMappings(String indexDirectory) { File[] files = (new File(indexDirectory)).listFiles(); if (files != null) { ArrayList cNames = new ArrayList<>(files.length); for (File file : files) { String name = file.getName(); if (name.endsWith(".tre")) { name = Basic.replaceFileSuffix(name, ""); for (String cName : ClassificationManager.getAllSupportedClassifications()) { if (cName.equalsIgnoreCase(name)) cNames.add(cName); } } } return cNames.toArray(new String[0]); } else return new String[0]; } } malt-0.5.2/src/malt/resources/000077500000000000000000000000001400455127600162155ustar00rootroot00000000000000malt-0.5.2/src/malt/resources/icons/000077500000000000000000000000001400455127600173305ustar00rootroot00000000000000malt-0.5.2/src/malt/resources/icons/malt-build.icns000066400000000000000000000242171400455127600222460ustar00rootroot00000000000000icns(is32 ]tkWkf۳G T_6([+ RLWﵵ7> _;nM2`0nZL5W _ݯAװbT0teS Rsa޵ R`|*7zoJǟZE{sux颙iFؘJ}^CĚʩGvrzֲۨ栙ᣌPƢsnmxl c XEz5BbWERiKK}ZeNYJSrhTFXF__Rl?_TQrQgnW]YfeQMrVcSdSUfHtTy}CT`dGh<[GN[QDnPcHARCr_KIfYM~EpSvYEx bs8mkil32  ˝ݢ53@0c2=>(L<-  -ǔ +;n,zS75+̧sQ 99-כnTT4.ékXQ .,̪m i yVU,Հ\ܚ6 5X,ܞ.YW,{ ]S,́m}V+U,ԟs}U (S-Ӫtvr?Q-vv Q,9iw R,爷zS=K,}P_0+]:+ +8+ x/-*䛚  n?DꄾF;)U$LF=8}!,}龍jF'323*,ޞsKowzzvZ㟦ܠ]J⟨Vx4*Rߟ:C~;4`;M/c|c.f馧xr'\R蟞f5qĢß=\.3;BɞD=2G14?B˞B<7L䐍K>¯ߟş>x(`ճşR,cնßzty,b鞥άzm?4^蚨>Zp2,RŸ:=>TQRSLEVSSNeELBFMSRRUD^KigNOeGU=LSRRVFYKgiNOfl;LSPXtQHSOtgNOgx7ETPYtPDCWeOOfUꥤnG\QQW|XGGJFPeD\6:CTLSSiMQ[OaEQeMA7M8ABtFD=>=AF9:=:>~ߕw;9:<>>=>F@;9kA?>@:=@=><<>Cw 9ED@?@@?=@A= CC??=?=??<=E ⾐>k򅦇y@=@>8>:89:;A<=:7=6=A~j;>:9^H@C>׊IEA<=A<<=;:=ƌB;>邦>=GAA>p:;>C>=@>EA@C:?>=?=DA8@GԆ=8::=@:?CE>HH=?>;>:<>9;863>@B;879̀6;A?>9;̀8Do>:̓A?=E@?>_XA@<=HEBiNBD;ME=>jFSE:ᾁ=DȡDB==?ٜ~==>>ACDuPJ?AAHSDBFCG}ENBNGFFGFIHHLJFAoIHGLGHKDFFEJL}R CNGDFJJIDGGARR LE@@CEGIEC?GRR ~俓RRRGrRR}FHMG@RRIM~ILDGERRHGJDEEBIRRRDIKpEFCRRRIgPEJRRRJۊNKBRRRRGFJ@RRRRBEE@RRRRC˒MGHRRRREKACCKKJRRRRHODuACDLHHRRRRMEIGEJA?AHGHRRRRMDHDGIAFDNLGRRRRJIEJE~PPHRRRRGO׆EBBRRRR@DH@RRRRCHHCRRRRGP@IRRRRAGGJRRRDAAHGHRRR>IHFDGHJRR̀RDIKGJGCRR̀RCOxCHRRREIRRGRR@ЃRKHJRRME@ARaYJLIRRDODCRgPJOHRRZLCDRmM]QERRŁRFRRǠOKABDٜB@A@h8mk malt-0.5.2/src/malt/resources/icons/malt-build16.png000066400000000000000000000076271400455127600222530ustar00rootroot00000000000000PNG  IHDR(-S pHYs   OiCCPPhotoshop ICC profilexڝSgTS=BKKoR RB&*! J!QEEȠQ, !{kּ> H3Q5 B.@ $pd!s#~<<+"x M0B\t8K@zB@F&S`cbP-`'{[! eDh;VEX0fK9-0IWfH  0Q){`##xFW<+*x<$9E[-qWW.(I+6aa@.y24x6_-"bbϫp@t~,/;m%h^ uf@Wp~<5j>{-]cK'Xto(hw?G%fIq^D$.Tʳ?D*A, `6B$BB dr`)B(Ͱ*`/@4Qhp.U=pa( Aa!ڈbX#!H$ ɈQ"K5H1RT UH=r9\F;2G1Q= C7F dt1r=6Ыhڏ>C03l0.B8, c˱" VcϱwE 6wB aAHXLXNH $4 7 Q'"K&b21XH,#/{C7$C2'ITFnR#,4H#dk9, +ȅ3![ b@qS(RjJ4e2AURݨT5ZBRQ4u9̓IKhhitݕNWGw Ljg(gwLӋT071oUX**| J&*/Tު UUT^S}FU3S ԖUPSSg;goT?~YYLOCQ_ cx,!k u5&|v*=9C3J3WRf?qtN (~))4L1e\kXHQG6EYAJ'\'GgSSݧ M=:.kDwn^Loy}/TmG X $ <5qo</QC]@Caaᄑ.ȽJtq]zۯ6iܟ4)Y3sCQ? 0k߬~OCOg#/c/Wװwa>>r><72Y_7ȷOo_C#dz%gA[z|!?:eAAA!h쐭!ΑiP~aa~ 'W?pX15wCsDDDޛg1O9-J5*>.j<74?.fYXXIlK9.*6nl {/]py.,:@LN8A*%w% yg"/6шC\*NH*Mz쑼5y$3,幄'L Lݛ:v m2=:1qB!Mggfvˬen/kY- BTZ(*geWf͉9+̳ې7ᒶKW-X潬j9(xoʿܔĹdff-[n ڴ VE/(ۻCɾUUMfeI?m]Nmq#׹=TR+Gw- 6 U#pDy  :v{vg/jBFS[b[O>zG499?rCd&ˮ/~јѡ򗓿m|x31^VwwO| (hSЧc3- cHRMz%u0`:o_FPLTEf3̙f3f3ffffff3f3333f333f3f3̙f3̙̙̙̙f̙3̙ffffff3f3333f333f3̙f3̙̙f3̙f3ff̙ffff3f33̙33f333̙f3ffffff3ffff̙fff3fffffff3ffffffffffff3fff3f3f3f3ff33f3ffffff3f3333f333333̙3f3333333f3333f3f3f3ff3f33f33333333f333333333f333f3̙f3f3ffffff3f3333f333f3ýqnd3x 1k1(ʠ]@FՆa#}-7L'JTǩf{Na'IENDB`malt-0.5.2/src/malt/resources/icons/malt-build32.png000066400000000000000000000102471400455127600222410ustar00rootroot00000000000000PNG  IHDR D pHYs   OiCCPPhotoshop ICC profilexڝSgTS=BKKoR RB&*! J!QEEȠQ, !{kּ> H3Q5 B.@ $pd!s#~<<+"x M0B\t8K@zB@F&S`cbP-`'{[! eDh;VEX0fK9-0IWfH  0Q){`##xFW<+*x<$9E[-qWW.(I+6aa@.y24x6_-"bbϫp@t~,/;m%h^ uf@Wp~<5j>{-]cK'Xto(hw?G%fIq^D$.Tʳ?D*A, `6B$BB dr`)B(Ͱ*`/@4Qhp.U=pa( Aa!ڈbX#!H$ ɈQ"K5H1RT UH=r9\F;2G1Q= C7F dt1r=6Ыhڏ>C03l0.B8, c˱" VcϱwE 6wB aAHXLXNH $4 7 Q'"K&b21XH,#/{C7$C2'ITFnR#,4H#dk9, +ȅ3![ b@qS(RjJ4e2AURݨT5ZBRQ4u9̓IKhhitݕNWGw Ljg(gwLӋT071oUX**| J&*/Tު UUT^S}FU3S ԖUPSSg;goT?~YYLOCQ_ cx,!k u5&|v*=9C3J3WRf?qtN (~))4L1e\kXHQG6EYAJ'\'GgSSݧ M=:.kDwn^Loy}/TmG X $ <5qo</QC]@Caaᄑ.ȽJtq]zۯ6iܟ4)Y3sCQ? 0k߬~OCOg#/c/Wװwa>>r><72Y_7ȷOo_C#dz%gA[z|!?:eAAA!h쐭!ΑiP~aa~ 'W?pX15wCsDDDޛg1O9-J5*>.j<74?.fYXXIlK9.*6nl {/]py.,:@LN8A*%w% yg"/6шC\*NH*Mz쑼5y$3,幄'L Lݛ:v m2=:1qB!Mggfvˬen/kY- BTZ(*geWf͉9+̳ې7ᒶKW-X潬j9(xoʿܔĹdff-[n ڴ VE/(ۻCɾUUMfeI?m]Nmq#׹=TR+Gw- 6 U#pDy  :v{vg/jBFS[b[O>zG499?rCd&ˮ/~јѡ򗓿m|x31^VwwO| (hSЧc3- cHRMz%u0`:o_FPLTEf3̙f3f3ffffff3f3333f333f3f3̙f3̙̙̙̙f̙3̙ffffff3f3333f333f3̙f3̙̙f3̙f3ff̙ffff3f33̙33f333̙f3ffffff3ffff̙fff3fffffff3ffffffffffff3fff3f3f3f3ff33f3ffffff3f3333f333333̙3f3333333f3333f3f3f3ff3f33f33333333f333333333f333f3̙f3f3ffffff3f3333f333f3W*[iuwkBtK.k.@^JmV_vjdO:FaÁz6ɶMM޵fniY\fdH06@#EsÞ[7 #롑`p4Mب>ګ$ .$9'əQ}~a/FڨUև(=7pyrq<|bq,e6꿷fգzIENDB`malt-0.5.2/src/malt/resources/icons/malt-build48.pdf000066400000000000000000001712741400455127600222450ustar00rootroot00000000000000%PDF-1.4 % 4 0 obj <> endobj xref 4 24 0000000016 00000 n 0000000948 00000 n 0000001008 00000 n 0000001346 00000 n 0000001532 00000 n 0000001566 00000 n 0000002555 00000 n 0000002702 00000 n 0000005351 00000 n 0000018828 00000 n 0000019059 00000 n 0000019255 00000 n 0000019550 00000 n 0000020808 00000 n 0000025889 00000 n 0000026955 00000 n 0000028023 00000 n 0000028240 00000 n 0000031114 00000 n 0000033767 00000 n 0000046178 00000 n 0000046213 00000 n 0000047259 00000 n 0000000776 00000 n trailer <<184C5F2819E94D61B340FDE47305442E>]/Prev 61938>> startxref 0 %%EOF 27 0 obj <>stream hb``e``va, Y8ؐDAgn#ke4.@{ _4@130|Ҍ@|  endstream endobj 5 0 obj <> endobj 6 0 obj <>/LastModified(D:20140710115558+01'00')/MediaBox[0.0 0.0 48.0 48.0]/Parent 1 0 R/PieceInfo<>>>/Resources 7 0 R/Rotate 0/Type/Page>> endobj 7 0 obj <>/ExtGState<>>>/Font<>/ProcSet[/PDF/Text/ImageB/ImageC/ImageI]/XObject<>>> endobj 8 0 obj [/ICCBased 11 0 R] endobj 9 0 obj <>stream q q 48 0 0 48 0 0 cm /Im0 Do q 0.2102222 0.1115394 m 0.2102222 0.1323879 l 0.3401616 0.1323879 l 0.3401616 0.1115394 l 0.2102222 0.1115394 l h 0.3190222 0.1146747 m 0.3190222 0.6617455 l 0.3398384 0.6617455 l 0.3398384 0.1146747 l 0.3190222 0.1146747 l h 0.3077737 0.6486222 m 0.3077737 0.6655596 l 0.3286222 0.6655596 l 0.3286222 0.6486222 l 0.3077737 0.6486222 l h 0.4691636 0.1001616 m 0.4691636 0.1210101 l 0.5634505 0.1210101 l 0.5634505 0.1001616 l 0.4691636 0.1001616 l h 0.5394020 0.1091798 m 0.5191354 0.1139960 l 0.6515960 0.6717657 l 0.6718626 0.6669495 l 0.5394020 0.1091798 l h 0.6828202 0.1064646 m 0.6828202 0.1273131 l 0.8077172 0.1273131 l 0.8077172 0.1064646 l 0.6828202 0.1064646 l h 0.7758788 0.1207838 m 0.7758788 0.8096242 l 0.7966949 0.8096242 l 0.7966949 0.1207838 l 0.7758788 0.1207838 l h W n /Im1 Do Q q BT 7 Tr /F1 1.0 Tf 1.00000 0.00000 0.00000 1.00000 0.13808 0.11845 Tm 0 0 Td (\000\060) Tj ET /Im2 Do Q Q Q endstream endobj 10 0 obj <> endobj 11 0 obj <>stream HyTSwoɞc [5, BHBK!aPVX=u:XKèZ\;v^N߽~w.MhaUZ>31[_& (DԬlK/jq'VOV?:OsRUzdWRab5? Ζ&VϳS7Trc3MQ]Ymc:B :Ŀ9ᝩ*UUZ<"2V[4ZLOMa?\⎽"?.KH6|zӷJ.Hǟy~Nϳ}Vdfc n~Y&+`;A4I d|(@zPZ@;=`=v0v <\$ x ^AD W P$@P>T !-dZP C; t @A/a<v}a1'X Mp'G}a|OY 48"BDH4)EH+ҍ "~rL"(*DQ)*E]a4zBgE#jB=0HIpp0MxJ$D1(%ˉ^Vq%],D"y"Hi$9@"m!#}FL&='dr%w{ȟ/_QXWJ%4R(cci+**FPvu? 6 Fs2hriStݓ.ҍu_џ0 7F4a`cfb|xn51)F]6{̤0]1̥& "rcIXrV+kuu5E4v}}Cq9JN')].uJ  wG x2^9{oƜchk`>b$eJ~ :Eb~,m,-Uݖ,Y¬*6X[ݱF=3뭷Y~dó Qti zf6~`{v.Ng#{}}c1X%6fmFN9NN8SΥ'g\\R]Z\t]\7u}&ps[6v_`) {Q5W=b _zžAe#``/VKPo !]#N}R|:|}n=/ȯo#JuW_ `$ 6+P-AܠԠUA' %8佐b8]+<q苰0C +_ XZ0nSPEUJ#JK#ʢi$aͷ**>2@ꨖОnu&kj6;k%G PApѳqM㽦5͊---SbhZKZO9uM/O\^W8i׹ĕ{̺]7Vھ]Y=&`͖5_ Ыbhו ۶^ Mw7n<< t|hӹ훩' ZL$h՛BdҞ@iءG&vVǥ8nRĩ7u\ЭD-u`ֲK³8%yhYѹJº;.! zpg_XQKFAǿ=ȼ:ɹ8ʷ6˶5̵5͵6ζ7ϸ9к<Ѿ?DINU\dlvۀ܊ݖޢ)߯6DScs 2F[p(@Xr4Pm8Ww)Km  endstream endobj 12 0 obj <>stream HU TTG~76nk15$dbf$"A(jD4܍+ ""( 4qљ̯S]]UUݪ->~e ׯ"#J %E2MFHЈy:g ;m*!gNKy֡.r䘨u@F{@! l0IF'.׶EZahVBIhx=l]ep)+}X)k r+usAnE͇OJMv5jֲwptrvZͽn:Yo06PL y6nҴY>-}[nӶ]O:tΟvY=>٫_үO~?;(`p 24tX##Gq'LaR)S3gϞ3w 8aeW\zMuI7lܴyVi)Rwٛ/}Ɓ̃9zx։٧N9{pksol/(F˱C&g: &BRE]-N<)F%͖J5v?&@@7MTvuA6r{IL,'mzI_[oЛM `eep4 OsC!(FE(vQ) oW頄+J2C,R*۔T%CT3Rd`lbl 2 U6$2RQGd7}+%iHSSW3H3_+dWYD#BoWw`n{ C*OJ }W6wYD&۰$,.*o\AK%f„[[zksata:,(t(/5 `|y 'ˏ7Vx+6_K KcRbGE;^la3a>i`N1w7w5w1+f`P;\b:k!Dx!i>īG>ͯ}l?#Ҫ6$!B$?a. {"X'(, V< q 98\E\N #,5\UW0 G1QhXDcs^LHZLF u !RHCZV7xKh9(JdCVJ-Q Jd+ZOh#mʹ6N;h'.Jݴqi64G鴟jR-ʠdOHNL.I6:DctRܩ.e GIGtQ2]IO2):M9t9:O)dt.eBWP#{(E!~_@>='TByszI5RyzKRAVZ؈jN5E-a/p…•S 7XO:!dsAɇZ+a E#) /T i؇,* L1L exX cZD?S4$m*iLpc(sACQ>UiNŒcE_+L+sģ9_\9û{~NqoƵB͹}OV8߲o]XSKΩR8qLщٸ9ۨw|¾3_ExMZ5}kԅ'z1o.Tul~1319J=Tg0!YxIȌ߅<.MWC+ 6hH,沞#y-/7M1o)%TUc1-Rgטeo3OtVG4B>0fz,|4^1G3nT.yHKeRa9|_ZlW~-Y r%b*cMM%WiH]79 5P 5g,u):&לe1VTV^n3Hsd̻JDm&kn;*wo%G`Z;!nm<؈U ;;DZ? ?-amle})mx.^kyBCJrJ`۾]6v>XXӭbֳɗrNw ra*8壎7DHXp1EќTDHaf9lNev'QKs9#9s.Pk]˲_ vFlmO hQYbإuo"SCo-˦&` " UY};fdt4}EikD6BR{6>;(ާe6AviR[Y5a|UuaіH j`Wĉ0rJO&`5X/m U'{Z[ˇQܹS,3swYƔ_4FQCHRM"YM&P+"Y[>̙4jL޼>=缳s1>4_ :_Ɯx:_8P\:kuRcfd:/٘IAF/m6?Mhyj7ҳStm=7k>ɣi*T5`\9T~s*˽T̅Ko1TfN7qd,^OGaedP\~H0s lAOED˰y}b"7L>{"}ĸ|/f|>y!n͹ٹsgY+̢/~q WƘNf:=s5& \M<Y3/:K&s'q;,5>u5 CU#ŵm,\]/)7YwWT|;2K O5o݉;4v_x5rO0.1to6dx6sOX:󽘽*Va5j5S99S1 ϼޠ Ӹ;+7wyZsfl>[<~/n kE7-1|ԭ8 ;)3B_Ο7џRGKWyR u,{+%0q;QлQJ_I0~_^};o7ߧ[@VN!k7o6oz:uY|rA+NjCi9crnZ63oGLo7(N4m},-Մ/2ckd.;AT -1:? OAÊM()ѽ2^PD6@qhu͛u-^gguY2ؠ%W9zW8O Z*nO<@KZ>xrRo)n_=evBI+N fo{UIK~(8|u&Ym77?+\zs&=yP1s-ňKM๰O1pbeaE1bĥcbĥcL1qNqfFƜ~<~Llo*63ѧZu nlPl/`>]'O?~֧bWb/faŠN3_(F|{YVЧC{aŠߞAňo/xI1Ӌsϫg[{yB[T{K =IUɢUeGbi<`ʠk%Y[*3t m@j4fecpDb{$ʚ/6*eHzO 55QѤE- g9˾VڃB+Eⓙ`U,<`m=;LDLgՎDMgR# oGWbCR9Joߑc*S;y/\ʠdnnjݾ$e\*ywˈD0$|͊_zM+*4K"yHV7`YzXwIFE3:j'ͨM,kS|Ѩ.cZI]WZo4'ح9*cP8$_.[ҥXOYHtLNI'H"κ'k{Ꝺ0DkL%gM_l>?.~ADʥ^|qX :[q[o[[Ekm:k߰k=A<1Zۃp4 R8 RqkkRj|x(lo[?Zk/9">^dV%wYAYo{[v_ZVrd ]3Tclk]KP~hGh{\ZaZ"av~dM^*7YULe{ BؾŖ3l@{Nsl飡[r7PLl:&a>5⍑ (iư("kǞ jNvv<wxIJ -ErRU!}$ UG?Cpmqw+R$+sg]37Ƅ2Wc}wפr^0OXf!#~rb͈*YRʏŠ/Z祅B#S C XvhT1|aCU.b?3T##ۗ(SPOeWY&fHqPy/WKilHZ&V}jʢJ)@%^l'ȨU8aQD]24㱐raiGxS) 1a-Öx8jkˣIeJV$5,9[T o5!1 k)APL'uU &V*=o3%j}nш*^/)ґjk񠕝^:{[uFL]+ehi$+o1d"ZR%c R e+Fc'AF jXcE9@7wWO% U-Hf!d^tmP6ִͫ vd6ʷU3IsJ<2%F3n?f+YUi<.9͖Z(vZP: EOTls,PyN4s u8oiY gRdWh)<5W(?>zF> r'qd"rOUfּEtrmo;EV;*`B=$eX=K!әt5?LT$NIDAR3LΆ%ն\.vTf_i9S)lf_IJggLpa3~gmh@8m]4<3VR[3RLŸV~j.H4K*wifv&.揤fBoxQjtA@_3ٍ)]JRȈǾkR<7T`nɘq>4k35Sc)sֱXSY좽סTpgt*fp|i 6+ VJóA]wjMu _+1]J;q+v{]&p.4l_ۨ-}2{PW+ PQˆ 6q$D JQrl&Cq8GhcqfqN)8t3q9؂s|\ q.%r\+q5p 7f܂v܁;q=~؉#؅GxOi؍/7{~/؃_~o$^`Mbma]c}6`C6bc6aS6cs`Kbka[c{v`Gvم]ٍك=ًه}ُYf@`PpHhXxNDNdNTpsg28s88 K˸+kg `> XHYÌ07q&d KYrn&Cyg |/eW_|omwc~O?/7{/_oýE+5ԒRGJ=/ 4DJ3i.-FJ;i/tt.UIw!=>WIdK e !2Tp!#e12Vx eL)2UrdL2Sre̖92W|Y e,%TrY!+e5V։_$ A1$_ PLY/ED$*1 $DJLel*[v77Hq)kޢVl8-N^ 14$q2}}$44DQDO7h{3#cS3s K+k[;{G'gW7w#U+M$ESk2N<0y(D:t+Kr=^բU*HtQ0;fB`C4r^e{Qj`צ` *q-T ѮJ\t ħŁBU-˔ɕmLsuؼu xH21Eߨ%ѥ%_ֺl˾k9cexJQ´W]$"B' Tzңw8zmO:nx8RH)hD:#IG9FUIUAUjH5凃(܃ԱYڳCQ%_6OmǬ# ]^KqvHDܔuSaJȄuYs ;Gu~R%(` (-\a"úrpNt뎆dQ)"EX|gڵ\pȽ Sy-S^(Ba؈kSj+Fk9kxbbWᩒx%Wji*~ +"2rӲqVnHg:[ٶm{.[=թ*?uOJ] 35 v7GffFxi ޫ 0,|R)QƗ(J*uDp94-{ +L^c~7ʸ(P=~ oE| Z:BӨX}O]"nUz]k $q"Jqb}ݔﰐ) Wh;p"2|FRO܄EFI4Jk"}9̝>nni_`j9j)-$f(|lZ_x/+,`o~^ޠZDpi$0 gg2Ž{xO9/XpQs2ϏR<P,|"QpVsÎ|p:+x*@ʯ>\*07v?GcI,h,nR5$D_- kؖղ9MB%"{\<>̾웣g L<{0`҃s0v~S̼剷3o^g̼SɎ{.Ukhe|v]epRWN{{l;}GTŠGp9,vPVSJu5T<2*Rk2 $7 PTAa&l>OuUnǡ-vߊ|/h<]xX`h1LEu~%'ܽ9GnFz-KV`S_n ~\^ULz}w TZBmnڢCډ+?nT~cm%l%7ei~۲~ZuA:|>'!⢹ >+wU<|wolAtY;Ts/6*(iKwdr:en /l.8l}Gijt{Z=r޺ƷF7hβ^BPԿ8pOܹtCx"n .o K^e:x7D ?${YI=0ܴ?h|Ov*}_3_^vw6=q۬ߵ+~LVfq2ӡ8Kqs7lO?74.{O< zf --=:L8=hJFdeDVFdeLVdeLɿ1c<&c]+KLY%.2JVZ4rU#?cv~>Yߝß+gu;|y~ v/x~ژ3|ڡpgB^eI m͢ ؆͚Bmcd3P&fJ -fV2XJś0!. P%TA!( gkkpN8iܹUpNWKJXiTr] zhp<lXaTp] g˹r#rI'O;4ŭD-붤[GnwJG;8QI @);(."'}JA8kfԪ֤ЗI?wh%~Hf`xj^4nTĬIL+,k`" F%CVH.+~/~TŊ3+?ב>I5pW᭒Qr4XRG`;}9Y3&}Ư=BN053ORr>;i,>ENoQwD]Дc߈e#}*%R |.=aQ>|y@ 8";fSm~;1T5gVu4hv"|}ʦh5(d3UHM ~&#{LP]IWlt[~_ lͭciP֗l4do,t2T$h>a;A592f;ͷޓtH>ؓ|`K$m 7+f!CvF@m??URןoYO-[%g޾e|q|> aN|ec{u=w#tX6w}>ק㌎{}92ErFϊq;@+{z/cv^ݓГf&YI֫{Q3ukdm=sJ ޞDUmYu}~zRU DfM'%ziIZ;xw~?Lc"a^`wwG ,D#}}($q ΎFa&"ɦ0 F#R endstream endobj 13 0 obj <> endobj 14 0 obj <>/CIDToGIDMap/Identity/FontDescriptor 13 0 R/Subtype/CIDFontType2/Type/Font>> endobj 15 0 obj <>stream H\Mj0O0weJ.$u{Yz$·D ^t@>$sn/-ǂC9iO' ї2t\hjyH4`>՘{0H">w%_ Z4E.TlcYZ3㭂O<㑠9Xl^t,DÏur˸/E"99eTOk endstream endobj 16 0 obj <>/Filter/FlateDecode/Height 48/Length 1008/Matte[1.0 1.0 1.0]/Name/X/Subtype/Image/Type/XObject/Width 48>>stream Hĕ{HSqOHQ[S^$22[3 dd2LSgh(K"_je3Y2[M;n^V9{?w9c`Sw_.XN ƭY rӘfE-FWE^Usف>%~~j 1*W!Sg'cNé_c7C8cXi{M빜C+#- ,%ْp-5v=Jo<$'j:MqZF) 3h遾L ^J;BI9yOXH;Uڤ0CpRE[SpM*rl8}F]4Rw=cGA!G&0ez$M*Y|)@ARǔd#NNc>/" irVy1q.߸U^jr2`E<'!)(.5TUy~dv9/-,GcUx^x_v&t8ypodˋVR;OdR.$ݒɖZzGnJپB?:+m:/3@X M ^Ws N>40,@a\b k䭻 !T]"u1tr9:e$LGXK\H66OEnqdX;C@J]`Hu) ^Vn^r\˃9b=\-r_񐾢a'&9j@:AQ+ K JEf}[xy>uHX0 Z K'åDhH # %kU endstream endobj 17 0 obj <>/Filter/DCTDecode/Height 48/ImageName/Untitled-1338954524269230.jpg/Intent/RelativeColorimetric/Length 4694/Name/Untitled-1338954524269230.jpg/SMask 16 0 R/Subtype/Image/Type/XObject/Width 48>>stream Adobed00     u!"1A2# QBa$3Rqb%C&4r 5'S6DTsEF7Gc(UVWdte)8fu*9:HIJXYZghijvwxyzm!1"AQ2aqB#Rb3 $Cr4%ScD&5T6Ed' sFtUeuV7)(GWf8vgwHXhx9IYiy*:JZjz ?ߺD+mv?[teDusBJ歬Uc0x{S d_'_UPL҉sUZUEI|9Nh{yz9V6y`%U STUE:)̏힀>-vLoۻjr!K8mmӜ5Wd8-%=(%2KI5L^uŵyCE8mjµ*q9٫mvNl]nc &?9S#MhQC*wÏ݉⧺BV;caaocc=|NŮ͙!pTXRad+inݣmX#Fq8' ՕORh3^+rٺm彶ѸK3,EټFb(/?9vncsu;LNki* ~!j棬媥 uyekWJبj?+k̖ϭTх(A@y=a۹8dǶ):dZ8o@| mMxͩ6m^FM"MّSQW$8}SSbdAyaZ^MqI?o}%m.cg“R^N4_O;>usn Y[lCg<Plj9&%/ᔢY2wZ `s4[ڴv11{@M(Q 4/7>KJuG\۷b=uvoxX.3IĊE2uj]?>콵AGit߸6JIy/rzH#,QC7@IXgea:Q<=/Ȗs#uxϬI~Rcc{F9㦙bb\N*Mp h7j{hu_|K޸ݙnir70vjە7531i3Tu} D0'Zf; rܺo ZՙNH9 xB:]f]$,7TK%Qqƣar2T!+zwӿ~w -Fw&=ܸǤܐv>{: ¢i7-&Z,~Y 'a\6de9+Z:Q!T6sxd{pK] IE$.g V_P Bqy;Y$rLƣ!&ӒXXvd݉'U'PGAbC8>|HsN"*=CVrq["*K;B\MTzsl|i^)~lG͟Zx-nTLn䱔,S]! VJ䃘aFk<.^9*Fpꬹ5yw^sn+ˋkM+ ݍBYc"'t kމzcu*&k9a奬*zzVc| (UTDk4Zעt=NT9)fܷ,9n~KMg1;ouZ˷+w#g2<oIRInKr&ކ*ꊚ_UXO ?)jeT@+BX*jq{FrNMɹnڴҘdz%>,ɎrCYO;Jߪ%@cfVN K+(u*"B[xh+ "Qu?CsCw$ h*C"aF'!?m}t2޶n=7>m!0.*m'QithUifc9԰  UUXՙ@ҺҤWMh`N[X {![x(xkUրN쏔=8osncq픦}3>#`aoC6,VgrY#SK].XWh.ƋTĂAk10z~N6[Kݻ'^'Dbm%Zg`Q<@Yv~~h2n-ĵ=׿w޹ONGyqxU'0TC]_Vvio I8?XhwwwsS7{Mk;+K IZBsDV7F3Y73??)`]4 \-i;3(7֎&Gdܙ~lf;{vT1TSCNZ .d pp+{dxR8cCJkUIQWcʼnk: endstream endobj 18 0 obj <>/Filter/DCTDecode/Height 48/Intent/RelativeColorimetric/Length 761/Name/X/Subtype/Image/Type/XObject/Width 48>>stream Adobed00     u!"1A2# QBa$3Rqb%C&4r 5'S6DTsEF7Gc(UVWdte)8fu*9:HIJXYZghijvwxyzm!1"AQ2aqB#Rb3 $Cr4%ScD&5T6Ed' sFtUeuV7)(GWf8vgwHXhx9IYiy*:JZjz ?=u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^ endstream endobj 19 0 obj <>/Filter/DCTDecode/Height 48/Intent/RelativeColorimetric/Length 763/Name/X/Subtype/Image/Type/XObject/Width 48>>stream Adobed00     u!"1A2# QBa$3Rqb%C&4r 5'S6DTsEF7Gc(UVWdte)8fu*9:HIJXYZghijvwxyzm!1"AQ2aqB#Rb3 $Cr4%ScD&5T6Ed' sFtUeuV7)(GWf8vgwHXhx9IYiy*:JZjz ?*gu{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^ endstream endobj 20 0 obj <> endobj 21 0 obj <>/Filter/FlateDecode/Height 48/Length 2615/Matte[1.0 1.0 1.0]/Name/X/SMask 25 0 R/Subtype/Image/Type/XObject/Width 48>>stream HܗyTT:;0 )UAQqs *kƴmrpK5-D-jhqUMցyoۚo6Qw~X:I2ڹ87yko+q B"/Nai\tW)$ތQL0+UāЏ4UL D oq[~1%#o*8w<8] ćRIO&^WPx9˗M̄PyPzQנG+3WFj;!ahEt,UXe0zR&o-*?'ϙw!1EREiznӌd(F"F 1H񉜩UT UTٱ *X?]W)˞>c"0XC0̲C Q~ȖZ? fXP# 0C4y0#_.dYu_K$( 122ӵ=lJo:04^)ꪳZ >!0֚DϽdմ7N-/* 6i[|t6G7<4*%F[coWlyidds_#F| ló TJ{F8vK۾[mʵ 0*r^A2l$ޙsayUBhr[,x@o!/-sh &FGH+dAY9zHhmI=|S[9fX<}}}xv6+º5 ߆XY}X/ix%Ǥ 6 qPxbBaƳ!~14o^-ŰZ\@8AgqMje311UViĭ=b4!ۺd!bƤ/ha ɭ umKsw#aʪ%QlZγd:u%O>7dM贩XMHj l:u%`X Do\˽SpNȐ>{JtݮŕC"Q3:GEÛӟDj` f`R7،-K.E Ou O=Lۍa]|LjaCGοPU#B*˴?zVTc5%%ŪO^u٣,Rɠ~6`gk'~GfE/Y֚ϵ9kVTL</LZitAcda 7VmMJJv "}㸖 |pJNk pv-y3yJlesyu 4ں~qSTUM3I$mk3\-)Ȉ& OBBOރ yKIF@Vġ.okP"Ҙc]=T>"0lRI |Q{p@yQ?c|An-]m!0"1XafDJ ᡌ?܉Uٛ7kon9pIn&=2kKI<>tbcSKn&J|{򾾾СCo^~}WU[' <"ˏ"p|l,,Y99l*z.;nwǎ<cvNNPAAh#ߏ_=arL\22>fdtoW3edd(xš-xl|߿9nWIIo 6Nտ?3Vfw:` 4 endstream endobj 22 0 obj <>stream HWiTSIGKXEi\>z F H[td#ĄZ& !^M\ 1>Q(.m0DlcДq_5|x#Gꅢv"4fv&Yؠ͹9GD=v@L!J3U] 4o!RkXka(3?u7|azB&{vn@dh!xx0.dȘh?WŚvZ')TMk)| *2;/1O;/p+T.>[s\]:Q@7 AƁЍhA6 B._'^F, d{h2A& WI!B2040"F2jnm3-u#ŁgEWs<dšK - ´[6n_UlY,TS|Vh\0ÜץDv:Kҝvt-Rզ=V+#n=c_>8TYԛ3=okO bx fwW,Wͦwyϥ;<1&T7}Fm|5Yvث*sԿ\x0?n抰_~5wUjPХr~⥸yKfAv+Te׮JVV5nPQC0 ׯ^q,~4ؿX̰}4Q 0+;Ua`}{oԏu0?dW?Y}(OOɧȁs QS? AO֟x^V8!˒;kɺ,Ih&ѶEs17ԀR>ױc{ey8y#Έ, 㧋>T&x_~^siռ W-(?v֮ !; dC5wTu S[ .m % $L&ݹ$fl5dn^K?T-G2͹whQp:$$ U과bukz&xӱlM7 W(r+} ESJF)gf]C+/wThS`H Zyw^gaE jNl[x['^vPѱA)הUfwKo((Qw^\XPA,q$nT<0W.p* 1ʠ=\ipf5 #o B@$`=8^^|_wxE#RĩCαcx~ϧ j:K! #$8|q6UMLv|#C+s+CCC+SS+S3`5Lp 03 יJ r0!fl4(&ez:_o@y.FlYp-qV䙇 Unc*(%0][1JhwQ.nE0SMqCr#_A`SQ LܴLHS s endstream endobj 23 0 obj <>stream HWkG[ۋ`ceba#wvlۻĻ~q;}㛝Yzf[#A@ 'a $ "'H@xH<0 ?@cΎ#ǭ4[UU LL-pY q6m=K{o <` Ps>>co3.Jz4[Wqv64 $n2 )-U}Sޯjq{b/xr_֗f6"ygO}g4&mIfxEWq*רn7<>h)sL1sZԭ/+ )lM ] W!7qt /R|G9OV,g GQ-aKt9Ogغ9ǎQ2kLnOz8v.ɶf!/Ym|N%x(UZbxyf4)Tj C@rE:d\\QMiA':w,ssx"crKZx͝}KjUTCgs'TfZx1"u,YIq]I۔t.&% k6nѶVNu٢lKr`JILþlQW,aZqިj[Eo) OKÝUpxGCes퇃 /kT8&y@} ?4yr +`_2$kf`ɦ\Oi,sLf"e-~Fi(j{TYu;47i4T]hX.yQ~t!ߟ!hX2t-'9,>O6x|,gN/p$xΜ)&· @ѝt 5n5ws$A:e7ԘL">ef0F.੧ %8";UQ#tc (> Ql N:` Hp aĽ4HLF;BT CSAh@;LB3;WkT6Sf:L@fN:Vtn$W>lUX7t JZ@+YtY V`ë* V* Pé0SJuR!&1T A5^;Y xH1#eI0R82(p4X?"qHR/Jgu-4fҭ:Յ8B/_q 5|4jUZW)v*w 'wS/)&3phh0G0{JJ*kGz̨8Lx!I?w|,"K=K1e&BLzmѐeh9GP"z,G`wWXkDd?H7`$ˆ'$~Da 4$+Gd(g c;%/`+OmxB(zO;A' Y Y$hJx|G}kcŔY0g E`cd q^ϫs>tcBBVHraWD(r}VN9 jĮ0H Ko:D;0*2$G` "܁H&4o"ITD1̌ 0FdG(z@[ǎob:*BҎ!A%uaFѻ f2Fj2Ϫ%p@`IF~ i!3 ) ,382X:uC$70+|f8}}f 8X+VRU}x8o 50z4̦P 8qyLS F7&:ͼ2R;k2XEhKf^ N\`'nˡqtGc6p2R;g4 ̔qACtn)~CqTW3A=$U`c.gq37P+Lj% ʫLa&hv8ղ˹9:[Ph !8/#4rբה+K|5M^;N[/85{{!+f0?WL|a46&hk/(L'8nטmz?+N'C[\==VݴnD%OʪI ښiᥚBGpM8>Ŏz }cl-A-XO™Z`m=v-쁾G{op5KCMD.eh'c5J/붛mF'SQzewo腠$Ll*lFawGcg=sp=A6C. nppK+pq5{^wd!^Φ9uk\~ \,`SS2A˦NG 68YG3F˧ekvja( 5YΊ8եmBxy_al8rvljKw]1s^2qVs6YZM; 4ǻ]3biqz8NK8ki Ď)s7]npܬnuydxkKxihJMwXj5ΡZw'K0"0;ߨ ZD*tb&A,O:yi=(@2Ήu>>b.u杌6T w[*_$x2[>mKUl;ȴa#TR^%W;TԴSJ\ܗTPY~QL Ԭ,UZ*MJ27+6$J{[~eKffZEAH |b+mױzsQ~2]TE'Ɨvg)8JEr1%s*mϙTvNRk<(Q=oRiT*IwD}MPʷ(2hK?(Teڶ *mm{اTrYRՎ)v:iZ5ݽg{Ͻw.5" ," >h5FJNߚX;_oĎRr}9}x^u UC[rWo D BNG:&|h*a<7C24q -eO4&=9|&"z@W DmUFlUǀWY7g3>ѾW; `q"gV$YR?F9]$xB~:Hzl X5v [3=?W1|TEsLLâN!0G:Xy}v[[§s/ǁ=J/F/rDk4J8\˻f)3!ՒGaO2BVߋ?kRݻ*Ȩitr?吚75Y+b躥~eE5ʹR[MTM D3M2]֙XX*:k7REbS ӥ3mރۄCMi>sAoH,v}Rt#Z*k6 1Ͱ͌7jL-!u~a1*uBPK0ۿCjfRB Άh'i,^2M5̴+I43Rc:T>K69i ƨa.-{4h)qR8I/SSV*J!G:R- >gkI GJ̊7XUM̺ry"BXCK$@r=u5/ -V$IgFLΣeξZ&Ķ,Ri4RL3 T %lX^ ,rWCj5aR|?!gL<۫Qzߎ)Nm2CBS&3- NAJV@@jngIPIU0LK{ .!rCCYJ  Z(*I3qH5*lҲdFڸL{EƊ8U{9ukP_W_6p+l:4X, s7:{TE&3( n~ eXY~]2/n  8 QujjF {nn?VeQ+'8!ue -uV@cVɚK+uuCJ?0T\iWf9PT? mRW$ZY7zI!wG|x_!WN A0mw]O ۙOzZgx_$yr r 12Jwk 0ˇ̽lmrTZ߼)gP߻kkS Z:\rz>o`Ng>M lwtq/ǙzǙzęqcq۹n;߶ݺvqB{P.gkpG'Angt?^uo/~<\Ïh%o>xD+I߮ 6!(¾q b͉ChrCbT#pU \&&iH,3w"3V+HLRѲPpIF!|iIQI/WN|A2D%(JQTF#^Pߑ86߇i(G2P@V1ụ*C(ʇ8 i0A$^"C*80i*7FR{ +m \dI]$E疌'3\桦쮙vW:xB7 VhG~ Z,`NUu{b"gΩSs ĠC_yӝp@[&]rqLu1J[ym+P\2_~5va]28"\w`KyGl+NصYŮ΂]/veWd7*Oth[5Tq> 1\SeЪr&#$2zFe$|C1='Yk̅DmxvC}ncO~4JUq.#p[V0tQ2>0>4~`?1~g^6Mt 9&6,S3s K+k[=}>EBF1zstqtiG}ot8鷍'Ə+5^B/ˣ ϏGOFLt0'ԩnYAߞyAˉϧQ^S_;U)#G's3`M1!dDO~aBe4ME'%`~З`lg/̵vwvK`mm7C#$7f=۱ JR.։Ze8 .ĻО۴ L`A|p/}U5wK< @Op8 Τg. zD6i? rD.|8VFqj Q5\pÂd4{< 'p5|s8K+C:#j&Vb/DI`6I5'g\yu$hLJN6 D* .{o5 q_p#IZ6<z9m9a bpB}4t#)l LyO %Ūfϣ`/#i霾saNMzHqㄚ75]ONǚ=붘C.n.v/gD솊lhwq܋t PzJE&tqG"%~rl=Cϥiɽ2b8:I]FN^8v)c0HW3ZCʆg1I-߁iƊH8*[fMII~2R%va9Ϊd Ѐ>+}_~llIcLB s v"hY+er8Jp!U8uj-,jzJeLhadcYLf+YW%y|b㉘m@"McL=Ȩ(SJ^IR5.TDwL(q[u3k IpIx1 V(h: ښb,*KbBpY$8&MbnGYZli+oX;2_>6?}"kv;!ݑn8kۛ!C)ùC@\:e0 ihӎ#kK$;veɍo u,uURɴ]'=ĚBu#$ :Ј8Vw.ݐ!!h?mwvUN?&@ty؅fڵ6Ki܇"S)8{)>B=N%Tڲct ?lQ>[Hm8&%ǽڸSW\p,Y9irfr qՉ\E ~=g_4԰6 /|ΊvD`Ů \5ksuËN`Z sӂ&j_15 T2jFRHjrm0+wxxTXwQmjCVhEw>bq d-o9X>T ž+z;e'+[U؏o/^;sӕիgB_xxxqmeL`W&+R]wB*ޮoQUQGsхKO{@@C&C#['\],[g!t'j:k -i@7Zcxi$ f%Wgb,4<?kK^+|Jsu ksEd1o-^BR OE rni-4eQeTY#U ieZH-`ł0R!<:k{TZih>#-W0Bɡ F3g:s"Pr.v⋙5`jQ2K[T>ϱx-:X}'s;!B၂6 !' A{‚2τ[Iy #wr̓/-Jv.liBpY,Af(ppM,`J7;VK\v+Wr۸܏D̤7N K2mƫnʇ'4ʘF˭/:6QJTQfӸtڡ),ްZɲ| VϤZh*Lt $jbMhf1=?B+.nDM 7JbFXr8TJLҗBs] n% ޫfhB!ݲE&v%7MV&́9J jҽΠhxLԴ%aOR۠\Vqתgf掃4~s#42 Ls2Ja$%/7 n:~ %aY֮fRg9tT3^dKSEuge;MY{DGQOi Ry,PJ~K *VNWHȥIS$Aޢm',IeXٜãyRMxMw*/꣈Q"mXsw|9ex%gJLҝh6doSJ z&ISaϪL]QPYҖ>ӻwL>f6:SCed&ᅘh[9A_W?JjK =IЊvU`z3kzч^JxشZb8&IB}7@:^kZ5=adqo\18II־bTcJR"4/Mzf-p36߿Տ#]]@$,k@F#ۀ@n/M*!@ŵ|ߖ ?RD*~8[o#[g#Ł^OQa]LW[N;ڜb/肑bYM[jLE\%-2U`) }>ᠭ&8^ӛ$^7AC"(*1T&CQ*"wj,';E{DOhrvY>;  k4HӇjdMW}+'a^[O=`fDp3Z+&;}l%#P>)Yy)\nDq\q@\~˘ Dr+V L,dDo׬L [);e ct ~BmDVo^83Ew49 >{g_n endstream endobj 24 0 obj [/ICCBased 26 0 R] endobj 25 0 obj <>/Filter/FlateDecode/Height 48/Length 816/Name/X/Subtype/Image/Type/XObject/Width 48>>stream Hb`x10NP`M@JHSyL 7@% | +#$-,@%_HdHUN7 Ls] u36qDs\ʝ7fPR6 ꛉ4z0a{PI<'Chxmb ]ruȶaM` aWϰ=.׼BY2-PSio\Pz"fL2>-A8BU#9Dd-PCyT猠۽/Q%LH! oV>JtaƼDVx2K^syDmy:z  >`Nw,Vn @yz8?Do $ I={P*`%7A<1t]SUaov* bMԟi VD2rXe<V"NIKs{]3xn2D9Fm2E]8DKꯝ%Ip 054 噣OYXIiwX9ۇJBʓ:fõ3b}Z W[$w8Ds߷>7gyB]G;n9`@0/2D.z)oPsEkp '2 %퉘yVJ%?3vO+Xl-,Un|Rj7܎Vǵ|jP@ëωVOE`pK endstream endobj 26 0 obj <>stream HyTSwoɞc [5, BHBK!aPVX=u:XKèZ\;v^N߽~w.MhaUZ>31[_& (DԬlK/jq'VOV?:OsRUzdWRab5? Ζ&VϳS7Trc3MQ]Ymc:B :Ŀ9ᝩ*UUZ<"2V[4ZLOMa?\⎽"?.KH6|zӷJ.Hǟy~Nϳ}Vdfc n~Y&+`;A4I d|(@zPZ@;=`=v0v <\$ x ^AD W P$@P>T !-dZP C; t @A/a<v}a1'X Mp'G}a|OY 48"BDH4)EH+ҍ "~rL"(*DQ)*E]a4zBgE#jB=0HIpp0MxJ$D1(%ˉ^Vq%],D"y"Hi$9@"m!#}FL&='dr%w{ȟ/_QXWJ%4R(cci+**FPvu? 6 Fs2hriStݓ.ҍu_џ0 7F4a`cfb|xn51)F]6{̤0]1̥& "rcIXrV+kuu5E4v}}Cq9JN')].uJ  wG x2^9{oƜchk`>b$eJ~ :Eb~,m,-Uݖ,Y¬*6X[ݱF=3뭷Y~dó Qti zf6~`{v.Ng#{}}c1X%6fmFN9NN8SΥ'g\\R]Z\t]\7u}&ps[6v_`) {Q5W=b _zžAe#``/VKPo !]#N}R|:|}n=/ȯo#JuW_ `$ 6+P-AܠԠUA' %8佐b8]+<q苰0C +_ XZ0nSPEUJ#JK#ʢi$aͷ**>2@ꨖОnu&kj6;k%G PApѳqM㽦5͊---SbhZKZO9uM/O\^W8i׹ĕ{̺]7Vھ]Y=&`͖5_ Ыbhו ۶^ Mw7n<< t|hӹ훩' ZL$h՛BdҞ@iءG&vVǥ8nRĩ7u\ЭD-u`ֲK³8%yhYѹJº;.! zpg_XQKFAǿ=ȼ:ɹ8ʷ6˶5̵5͵6ζ7ϸ9к<Ѿ?DINU\dlvۀ܊ݖޢ)߯6DScs 2F[p(@Xr4Pm8Ww)Km  endstream endobj 1 0 obj <> endobj 2 0 obj <>stream Adobe Photoshop CS5 Macintosh 2014-07-10T11:55:56+02:00 2014-07-10T11:55:58+02:00 2014-07-10T11:55:58+02:00 JPEG 48 48 /9j/4gxYSUNDX1BST0ZJTEUAAQEAAAxITGlubwIQAABtbnRyUkdCIFhZWiAHzgACAAkABgAxAABh Y3NwTVNGVAAAAABJRUMgc1JHQgAAAAAAAAAAAAAAAAAA9tYAAQAAAADTLUhQICAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABFjcHJ0AAABUAAAADNkZXNjAAAB hAAAAGx3dHB0AAAB8AAAABRia3B0AAACBAAAABRyWFlaAAACGAAAABRnWFlaAAACLAAAABRiWFla AAACQAAAABRkbW5kAAACVAAAAHBkbWRkAAACxAAAAIh2dWVkAAADTAAAAIZ2aWV3AAAD1AAAACRs dW1pAAAD+AAAABRtZWFzAAAEDAAAACR0ZWNoAAAEMAAAAAxyVFJDAAAEPAAACAxnVFJDAAAEPAAA CAxiVFJDAAAEPAAACAx0ZXh0AAAAAENvcHlyaWdodCAoYykgMTk5OCBIZXdsZXR0LVBhY2thcmQg Q29tcGFueQAAZGVzYwAAAAAAAAASc1JHQiBJRUM2MTk2Ni0yLjEAAAAAAAAAAAAAABJzUkdCIElF QzYxOTY2LTIuMQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAWFlaIAAAAAAAAPNRAAEAAAABFsxYWVogAAAAAAAAAAAAAAAAAAAAAFhZWiAAAAAAAABvogAA OPUAAAOQWFlaIAAAAAAAAGKZAAC3hQAAGNpYWVogAAAAAAAAJKAAAA+EAAC2z2Rlc2MAAAAAAAAA FklFQyBodHRwOi8vd3d3LmllYy5jaAAAAAAAAAAAAAAAFklFQyBodHRwOi8vd3d3LmllYy5jaAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABkZXNjAAAAAAAAAC5J RUMgNjE5NjYtMi4xIERlZmF1bHQgUkdCIGNvbG91ciBzcGFjZSAtIHNSR0IAAAAAAAAAAAAAAC5J RUMgNjE5NjYtMi4xIERlZmF1bHQgUkdCIGNvbG91ciBzcGFjZSAtIHNSR0IAAAAAAAAAAAAAAAAA AAAAAAAAAAAAZGVzYwAAAAAAAAAsUmVmZXJlbmNlIFZpZXdpbmcgQ29uZGl0aW9uIGluIElFQzYx OTY2LTIuMQAAAAAAAAAAAAAALFJlZmVyZW5jZSBWaWV3aW5nIENvbmRpdGlvbiBpbiBJRUM2MTk2 Ni0yLjEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHZpZXcAAAAAABOk/gAUXy4AEM8UAAPtzAAE EwsAA1yeAAAAAVhZWiAAAAAAAEwJVgBQAAAAVx/nbWVhcwAAAAAAAAABAAAAAAAAAAAAAAAAAAAA AAAAAo8AAAACc2lnIAAAAABDUlQgY3VydgAAAAAAAAQAAAAABQAKAA8AFAAZAB4AIwAoAC0AMgA3 ADsAQABFAEoATwBUAFkAXgBjAGgAbQByAHcAfACBAIYAiwCQAJUAmgCfAKQAqQCuALIAtwC8AMEA xgDLANAA1QDbAOAA5QDrAPAA9gD7AQEBBwENARMBGQEfASUBKwEyATgBPgFFAUwBUgFZAWABZwFu AXUBfAGDAYsBkgGaAaEBqQGxAbkBwQHJAdEB2QHhAekB8gH6AgMCDAIUAh0CJgIvAjgCQQJLAlQC XQJnAnECegKEAo4CmAKiAqwCtgLBAssC1QLgAusC9QMAAwsDFgMhAy0DOANDA08DWgNmA3IDfgOK A5YDogOuA7oDxwPTA+AD7AP5BAYEEwQgBC0EOwRIBFUEYwRxBH4EjASaBKgEtgTEBNME4QTwBP4F DQUcBSsFOgVJBVgFZwV3BYYFlgWmBbUFxQXVBeUF9gYGBhYGJwY3BkgGWQZqBnsGjAadBq8GwAbR BuMG9QcHBxkHKwc9B08HYQd0B4YHmQesB78H0gflB/gICwgfCDIIRghaCG4IggiWCKoIvgjSCOcI +wkQCSUJOglPCWQJeQmPCaQJugnPCeUJ+woRCicKPQpUCmoKgQqYCq4KxQrcCvMLCwsiCzkLUQtp C4ALmAuwC8gL4Qv5DBIMKgxDDFwMdQyODKcMwAzZDPMNDQ0mDUANWg10DY4NqQ3DDd4N+A4TDi4O SQ5kDn8Omw62DtIO7g8JDyUPQQ9eD3oPlg+zD88P7BAJECYQQxBhEH4QmxC5ENcQ9RETETERTxFt EYwRqhHJEegSBxImEkUSZBKEEqMSwxLjEwMTIxNDE2MTgxOkE8UT5RQGFCcUSRRqFIsUrRTOFPAV EhU0FVYVeBWbFb0V4BYDFiYWSRZsFo8WshbWFvoXHRdBF2UXiReuF9IX9xgbGEAYZRiKGK8Y1Rj6 GSAZRRlrGZEZtxndGgQaKhpRGncanhrFGuwbFBs7G2MbihuyG9ocAhwqHFIcexyjHMwc9R0eHUcd cB2ZHcMd7B4WHkAeah6UHr4e6R8THz4faR+UH78f6iAVIEEgbCCYIMQg8CEcIUghdSGhIc4h+yIn IlUigiKvIt0jCiM4I2YjlCPCI/AkHyRNJHwkqyTaJQklOCVoJZclxyX3JicmVyaHJrcm6CcYJ0kn eierJ9woDSg/KHEooijUKQYpOClrKZ0p0CoCKjUqaCqbKs8rAis2K2krnSvRLAUsOSxuLKIs1y0M LUEtdi2rLeEuFi5MLoIuty7uLyQvWi+RL8cv/jA1MGwwpDDbMRIxSjGCMbox8jIqMmMymzLUMw0z RjN/M7gz8TQrNGU0njTYNRM1TTWHNcI1/TY3NnI2rjbpNyQ3YDecN9c4FDhQOIw4yDkFOUI5fzm8 Ofk6Njp0OrI67zstO2s7qjvoPCc8ZTykPOM9Ij1hPaE94D4gPmA+oD7gPyE/YT+iP+JAI0BkQKZA 50EpQWpBrEHuQjBCckK1QvdDOkN9Q8BEA0RHRIpEzkUSRVVFmkXeRiJGZ0arRvBHNUd7R8BIBUhL SJFI10kdSWNJqUnwSjdKfUrESwxLU0uaS+JMKkxyTLpNAk1KTZNN3E4lTm5Ot08AT0lPk0/dUCdQ cVC7UQZRUFGbUeZSMVJ8UsdTE1NfU6pT9lRCVI9U21UoVXVVwlYPVlxWqVb3V0RXklfgWC9YfVjL WRpZaVm4WgdaVlqmWvVbRVuVW+VcNVyGXNZdJ114XcleGl5sXr1fD19hX7NgBWBXYKpg/GFPYaJh 9WJJYpxi8GNDY5dj62RAZJRk6WU9ZZJl52Y9ZpJm6Gc9Z5Nn6Wg/aJZo7GlDaZpp8WpIap9q92tP a6dr/2xXbK9tCG1gbbluEm5rbsRvHm94b9FwK3CGcOBxOnGVcfByS3KmcwFzXXO4dBR0cHTMdSh1 hXXhdj52m3b4d1Z3s3gReG54zHkqeYl553pGeqV7BHtje8J8IXyBfOF9QX2hfgF+Yn7CfyN/hH/l gEeAqIEKgWuBzYIwgpKC9INXg7qEHYSAhOOFR4Wrhg6GcobXhzuHn4gEiGmIzokziZmJ/opkisqL MIuWi/yMY4zKjTGNmI3/jmaOzo82j56QBpBukNaRP5GokhGSepLjk02TtpQglIqU9JVflcmWNJaf lwqXdZfgmEyYuJkkmZCZ/JpomtWbQpuvnByciZz3nWSd0p5Anq6fHZ+Ln/qgaaDYoUehtqImopaj BqN2o+akVqTHpTilqaYapoum/adup+CoUqjEqTepqaocqo+rAqt1q+msXKzQrUStuK4trqGvFq+L sACwdbDqsWCx1rJLssKzOLOutCW0nLUTtYq2AbZ5tvC3aLfguFm40blKucK6O7q1uy67p7whvJu9 Fb2Pvgq+hL7/v3q/9cBwwOzBZ8Hjwl/C28NYw9TEUcTOxUvFyMZGxsPHQce/yD3IvMk6ybnKOMq3 yzbLtsw1zLXNNc21zjbOts83z7jQOdC60TzRvtI/0sHTRNPG1EnUy9VO1dHWVdbY11zX4Nhk2OjZ bNnx2nba+9uA3AXcit0Q3ZbeHN6i3ynfr+A24L3hROHM4lPi2+Nj4+vkc+T85YTmDeaW5x/nqegy 6LzpRunQ6lvq5etw6/vshu0R7ZzuKO6070DvzPBY8OXxcvH/8ozzGfOn9DT0wvVQ9d72bfb794r4 Gfio+Tj5x/pX+uf7d/wH/Jj9Kf26/kv+3P9t////7QAMQWRvYmVfQ00AAf/uAA5BZG9iZQBkgAAA AAH/2wCEAAwICAgJCAwJCQwRCwoLERUPDAwPFRgTExUTExgRDAwMDAwMEQwMDAwMDAwMDAwMDAwM DAwMDAwMDAwMDAwMDAwBDQsLDQ4NEA4OEBQODg4UFA4ODg4UEQwMDAwMEREMDAwMDAwRDAwMDAwM DAwMDAwMDAwMDAwMDAwMDAwMDAwMDP/AABEIADAAMAMBIgACEQEDEQH/3QAEAAP/xAE/AAABBQEB AQEBAQAAAAAAAAADAAECBAUGBwgJCgsBAAEFAQEBAQEBAAAAAAAAAAEAAgMEBQYHCAkKCxAAAQQB AwIEAgUHBggFAwwzAQACEQMEIRIxBUFRYRMicYEyBhSRobFCIyQVUsFiMzRygtFDByWSU/Dh8WNz NRaisoMmRJNUZEXCo3Q2F9JV4mXys4TD03Xj80YnlKSFtJXE1OT0pbXF1eX1VmZ2hpamtsbW5vY3 R1dnd4eXp7fH1+f3EQACAgECBAQDBAUGBwcGBTUBAAIRAyExEgRBUWFxIhMFMoGRFKGxQiPBUtHw MyRi4XKCkkNTFWNzNPElBhaisoMHJjXC0kSTVKMXZEVVNnRl4vKzhMPTdePzRpSkhbSVxNTk9KW1 xdXl9VZmdoaWprbG1ub2JzdHV2d3h5ent8f/2gAMAwEAAhEDEQA/APVVlda+sfT+jbG5O59tmra2 CTtmC/3bWpvrH15nRMNl+wW22vDK6yYn86w7v5LF5/1nqNfVOpW51bHVtuDPY6JBaxlbuP5TE2cq 82Lms55bFHPLGcmOU/b0l7evDKXzcM/3XsvrJ9aqsHBqs6bfTbkWvb7CdxFZa52/02uDuzfpInRv rTi5HRjmZ99TcmkPddUwgOhpds21E7v0jVkfVX6udH6p092Tl1Pfay11Z/SODTAY8Oa2v09v85+8 q31u6H0vpAxvsVTmPvL5JeXNAZs02v3O93qf6RC5VxdOzJLnOWHJDNwyMqjk+svT7XH/AHv0/beu 6L17B6zS6zF3NfXHq1PEObP0f5LuPzVpLyvovVH9KzDmNBcGsIcwGNwJb7Sf6y9D6D1evrHTmZjW 7HyWWsBna8fSEoxlY8VnLZjzGH344zjgZGFcXuaj+twwf//Q7D629d6XViZXS7h6mW6qa2lktDnD 2P3/AJrm/SXB4zS2yoOEe5p+RO5v/RXd/W36v9NvxcrqtjjXlMq9hLoYSwexm0/v/QXCYznPtqnn c1o+AIa1RTuxbD8U4fuWHhv/AHRj47/e4MnyvW/UZ/2bqmf0+N3t3epx/MvNX0P5fr/vKt9aHHN+ tLMYDaaW11AzMz+sbv5P89sRrz9i+uHT8r+c+110nb9Hb6jPsPPu3bdvq/8AQVb6tj9pfWSzMLdo dY+81n3AbnG0Ddpu2O/ko9o+LnQ/yfKfucxL/wAJx/rf+6/fedH83b/UP5QvR/q517pWdVXhYY9O 6qkPfUGbWiNrbNn5v03rzgfzdn9X+LV6N9XPq/03p9debiuNl1tIY+zfuaZ2us2R/LYljtv/AAng /wBHjivi9yfDW36PzP8A/9HvvrL0Idbwm0NeKrqnh9b3SQPzXhwH8hcF1LpzOk9WdhVvNooNZ3uE ElzWWu0H8p69UWF1b6p4nUst2Z6jqrn7dxjcCWgMGm5v5rU2Ub2YOcx5M+LHijICMMsctS/q8X/f PLdf61g9VwMJtRfXkYc1vpc2QQQ0GxtrTt9rqfo/8IodD67R0Sqy91ZvvsDm1sGgE7Pc9/8A1v8A NXR9f+qVN+DSzpePSzKqe3c8wwvYGuY7e9o9znO2OU+k/VPEb0p1HUset2VcHB72w4sB9rPSsI9r tvv/AK6HCeInwXjlcQ5z71/k5R4Tjv8AWe58t/7Pgi8X0rB/aGWMMP8ATNw2h8TBJH5q9G6B0hvR +msww7e+S+14mC930ts/mrP6b9TsXAyaslt73vqIPETHzcuhRjGgs5LFkwcv7MpA+oz9Py6+b//Z 3 sRGB IEC61966-2.1 M M application/pdf uuid:fbdf0695-460e-b040-90a6-a61434dd3ab0 xmp.did:018011740720681188C6B0D5E8A0D526 xmp.did:068011740720681188C6B0D5E8A0D526 created xmp.iid:068011740720681188C6B0D5E8A0D526 2014-07-10T11:55:56+02:00 Adobe Photoshop CS5 Macintosh Adobe Photoshop for Macintosh -- Image Conversion Plug-in endstream endobj 3 0 obj <> endobj xref 0 4 0000000000 65535 f 0000049908 00000 n 0000049959 00000 n 0000061739 00000 n trailer <<184C5F2819E94D61B340FDE47305442E>]>> startxref 116 %%EOF malt-0.5.2/src/malt/resources/icons/malt-build48.png000066400000000000000000000125431400455127600222510ustar00rootroot00000000000000PNG  IHDR00W pHYs   OiCCPPhotoshop ICC profilexڝSgTS=BKKoR RB&*! J!QEEȠQ, !{kּ> H3Q5 B.@ $pd!s#~<<+"x M0B\t8K@zB@F&S`cbP-`'{[! eDh;VEX0fK9-0IWfH  0Q){`##xFW<+*x<$9E[-qWW.(I+6aa@.y24x6_-"bbϫp@t~,/;m%h^ uf@Wp~<5j>{-]cK'Xto(hw?G%fIq^D$.Tʳ?D*A, `6B$BB dr`)B(Ͱ*`/@4Qhp.U=pa( Aa!ڈbX#!H$ ɈQ"K5H1RT UH=r9\F;2G1Q= C7F dt1r=6Ыhڏ>C03l0.B8, c˱" VcϱwE 6wB aAHXLXNH $4 7 Q'"K&b21XH,#/{C7$C2'ITFnR#,4H#dk9, +ȅ3![ b@qS(RjJ4e2AURݨT5ZBRQ4u9̓IKhhitݕNWGw Ljg(gwLӋT071oUX**| J&*/Tު UUT^S}FU3S ԖUPSSg;goT?~YYLOCQ_ cx,!k u5&|v*=9C3J3WRf?qtN (~))4L1e\kXHQG6EYAJ'\'GgSSݧ M=:.kDwn^Loy}/TmG X $ <5qo</QC]@Caaᄑ.ȽJtq]zۯ6iܟ4)Y3sCQ? 0k߬~OCOg#/c/Wװwa>>r><72Y_7ȷOo_C#dz%gA[z|!?:eAAA!h쐭!ΑiP~aa~ 'W?pX15wCsDDDޛg1O9-J5*>.j<74?.fYXXIlK9.*6nl {/]py.,:@LN8A*%w% yg"/6шC\*NH*Mz쑼5y$3,幄'L Lݛ:v m2=:1qB!Mggfvˬen/kY- BTZ(*geWf͉9+̳ې7ᒶKW-X潬j9(xoʿܔĹdff-[n ڴ VE/(ۻCɾUUMfeI?m]Nmq#׹=TR+Gw- 6 U#pDy  :v{vg/jBFS[b[O>zG499?rCd&ˮ/~јѡ򗓿m|x31^VwwO| (hSЧc3- cHRMz%u0`:o_F IDATxZ{pTsc7l^lYD@ €ZlNG*P"TSX[ADE^"!< $lMBfٽsH֙;sg39{7H)c? ;_7[#+j=sV(Kw"UO;E1)DTS(.t?WX*D} PTN{EK8\SBIHďvP {(r]U@0%I$8bߘ%I?9o_(5UӀ9:xGJg[jOхd2c4qĔiT#4wB8[F9W4UH#Yua+e,2m-Շ\T hi'b4Ip E _ ΁ÌOm쐊[ȖQAbKFV6fh]8AY?H- R fY, di0;G,7O'*mCSb~!l:LΡTAm\yW+@ٺp~8tt@-HB;X+L+uo>tqq:@e^$M "Z2= Xe(F( ²iDPK4(b9"99Ϣ!G;݁]ŀ /KJfjjPIB!3O^R)7%)5Q3uʷ 2 1Zs?PTd ݛV\S3xR~#+:=w^ZŽ/nk@:9Y OCPZ% bҿ{;2R,.pη(cH@c*ЙSMNLqrCUѪ)hM(Ϥ !v anCDiVe )}LTj('V=gL›p΀ >'%.l))MHKjF i^oEso[tc_>D7͘}/a56I˲Kݰ1nt?Ps^WaPJ;M۵qYG?eOҋ<^~ 6Aװ_S8ٳzB[Gt %w.4wt]6w)"aF6bd/v" 'fc!Шf`D - مM ==%RAb KN/XRclG`^>cW[FAWKbQc$+qIWXIOUUVO}FTSP[4TQRSL|FUQV?BXQRSMzFTRVF@VRSSPpFPgkK^+WOsWQMw6sGPjbJY3WKwYQMw>zGQeRR H3Q5 B.@ $pd!s#~<<+"x M0B\t8K@zB@F&S`cbP-`'{[! eDh;VEX0fK9-0IWfH  0Q){`##xFW<+*x<$9E[-qWW.(I+6aa@.y24x6_-"bbϫp@t~,/;m%h^ uf@Wp~<5j>{-]cK'Xto(hw?G%fIq^D$.Tʳ?D*A, `6B$BB dr`)B(Ͱ*`/@4Qhp.U=pa( Aa!ڈbX#!H$ ɈQ"K5H1RT UH=r9\F;2G1Q= C7F dt1r=6Ыhڏ>C03l0.B8, c˱" VcϱwE 6wB aAHXLXNH $4 7 Q'"K&b21XH,#/{C7$C2'ITFnR#,4H#dk9, +ȅ3![ b@qS(RjJ4e2AURݨT5ZBRQ4u9̓IKhhitݕNWGw Ljg(gwLӋT071oUX**| J&*/Tު UUT^S}FU3S ԖUPSSg;goT?~YYLOCQ_ cx,!k u5&|v*=9C3J3WRf?qtN (~))4L1e\kXHQG6EYAJ'\'GgSSݧ M=:.kDwn^Loy}/TmG X $ <5qo</QC]@Caaᄑ.ȽJtq]zۯ6iܟ4)Y3sCQ? 0k߬~OCOg#/c/Wװwa>>r><72Y_7ȷOo_C#dz%gA[z|!?:eAAA!h쐭!ΑiP~aa~ 'W?pX15wCsDDDޛg1O9-J5*>.j<74?.fYXXIlK9.*6nl {/]py.,:@LN8A*%w% yg"/6шC\*NH*Mz쑼5y$3,幄'L Lݛ:v m2=:1qB!Mggfvˬen/kY- BTZ(*geWf͉9+̳ې7ᒶKW-X潬j9(xoʿܔĹdff-[n ڴ VE/(ۻCɾUUMfeI?m]Nmq#׹=TR+Gw- 6 U#pDy  :v{vg/jBFS[b[O>zG499?rCd&ˮ/~јѡ򗓿m|x31^VwwO| (hSЧc3- cHRMz%u0`:o_FPLTEf3̙f3f3ffffff3f3333f333f3f3̙f3̙̙̙̙f̙3̙ffffff3f3333f333f3̙f3̙̙f3̙f3ff̙ffff3f33̙33f333̙f3ffffff3ffff̙fff3fffffff3ffffffffffff3fff3f3f3f3ff33f3ffffff3f3333f333333̙3f3333333f3333f3f3f3ff3f33f33333333f333333333f333f3̙f3f3ffffff3f3333f333f3*+ $ X`*aL"Z+iQέ K>HaEM۝MMs1tRNSS%IDATx4AJP4B]n FApE9^(^Cx"*ZkG | ߘ8~$,⡊}c+M'XLG@1 0|}߇ p䨙2ޮ(o2L+o{A;i2 @_K},~$GsWesy%)i-HԽh`X[IENDB`malt-0.5.2/src/malt/resources/icons/malt-run32.png000066400000000000000000000101501400455127600217370ustar00rootroot00000000000000PNG  IHDR D pHYs   OiCCPPhotoshop ICC profilexڝSgTS=BKKoR RB&*! J!QEEȠQ, !{kּ> H3Q5 B.@ $pd!s#~<<+"x M0B\t8K@zB@F&S`cbP-`'{[! eDh;VEX0fK9-0IWfH  0Q){`##xFW<+*x<$9E[-qWW.(I+6aa@.y24x6_-"bbϫp@t~,/;m%h^ uf@Wp~<5j>{-]cK'Xto(hw?G%fIq^D$.Tʳ?D*A, `6B$BB dr`)B(Ͱ*`/@4Qhp.U=pa( Aa!ڈbX#!H$ ɈQ"K5H1RT UH=r9\F;2G1Q= C7F dt1r=6Ыhڏ>C03l0.B8, c˱" VcϱwE 6wB aAHXLXNH $4 7 Q'"K&b21XH,#/{C7$C2'ITFnR#,4H#dk9, +ȅ3![ b@qS(RjJ4e2AURݨT5ZBRQ4u9̓IKhhitݕNWGw Ljg(gwLӋT071oUX**| J&*/Tު UUT^S}FU3S ԖUPSSg;goT?~YYLOCQ_ cx,!k u5&|v*=9C3J3WRf?qtN (~))4L1e\kXHQG6EYAJ'\'GgSSݧ M=:.kDwn^Loy}/TmG X $ <5qo</QC]@Caaᄑ.ȽJtq]zۯ6iܟ4)Y3sCQ? 0k߬~OCOg#/c/Wװwa>>r><72Y_7ȷOo_C#dz%gA[z|!?:eAAA!h쐭!ΑiP~aa~ 'W?pX15wCsDDDޛg1O9-J5*>.j<74?.fYXXIlK9.*6nl {/]py.,:@LN8A*%w% yg"/6шC\*NH*Mz쑼5y$3,幄'L Lݛ:v m2=:1qB!Mggfvˬen/kY- BTZ(*geWf͉9+̳ې7ᒶKW-X潬j9(xoʿܔĹdff-[n ڴ VE/(ۻCɾUUMfeI?m]Nmq#׹=TR+Gw- 6 U#pDy  :v{vg/jBFS[b[O>zG499?rCd&ˮ/~јѡ򗓿m|x31^VwwO| (hSЧc3- cHRMz%u0`:o_FPLTEf3̙f3f3ffffff3f3333f333f3f3̙f3̙̙̙̙f̙3̙ffffff3f3333f333f3̙f3̙̙f3̙f3ff̙ffff3f33̙33f333̙f3ffffff3ffff̙fff3fffffff3ffffffffffff3fff3f3f3f3ff33f3ffffff3f3333f333333̙3f3333333f3333f3f3f3ff3f33f33333333f333333333f333f3̙f3f3ffffff3f3333f333f3ⴵ^ԩRSˆ^wFRc8}\P[tRNSS%{IDATxڄ1kPyѤ+:9:ԱCN.?@7(8 ~'[R*ZM:ļpwϽwH>Cɛ"k$o&@qx7H `ku:Bi亣6^[2\Dۀ5PWP.Yc/wn9z zF84.Z؆hO5>XszpxXTGŽ=OmXՈhK g,PY:Oߎ ɧ[(|òmK !,¸GFԍlί]RfMDqàJ≈Ʀ[inyMŒlSE߮ȖIENDB`malt-0.5.2/src/malt/resources/icons/malt-run48.pdf000066400000000000000000001064061400455127600217450ustar00rootroot00000000000000%PDF-1.4 % 4 0 obj <> endobj xref 4 21 0000000016 00000 n 0000000876 00000 n 0000000936 00000 n 0000001274 00000 n 0000001463 00000 n 0000001497 00000 n 0000002431 00000 n 0000005080 00000 n 0000005845 00000 n 0000011004 00000 n 0000013605 00000 n 0000015221 00000 n 0000016731 00000 n 0000020265 00000 n 0000020482 00000 n 0000022074 00000 n 0000023286 00000 n 0000025090 00000 n 0000025125 00000 n 0000025856 00000 n 0000000716 00000 n trailer <<61014550B4474CC29B0DD19515D6CDDE>]/Prev 35900>> startxref 0 %%EOF 24 0 obj <>stream hb``b``` Y8ńAAtw^*vBbnz@4030t]v YH endstream endobj 5 0 obj <> endobj 6 0 obj <>/LastModified(D:20140711101603+01'00')/MediaBox[0.0 0.0 48.0 48.0]/Parent 1 0 R/PieceInfo<>>>/Resources 7 0 R/Rotate 0/Type/Page>> endobj 7 0 obj <>/ExtGState<>>>/ProcSet[/PDF/Text/ImageB/ImageC/ImageI]/XObject<>>> endobj 8 0 obj [/ICCBased 10 0 R] endobj 9 0 obj <>stream q q 48 0 0 48 0 0 cm /Im0 Do q 0.4369915 0.0136965 m 0.4281658 0.0225223 l 0.9106061 0.5049949 l 0.9194318 0.4961369 l 0.4369915 0.0136965 l h W* n /Im1 Do Q q 0.6656889 0.8464727 m 0.6865374 0.8464727 l 0.6866667 0.1437333 l 0.6658505 0.1437333 l 0.6656889 0.8464727 l h W* n /Im2 Do Q q 0.5763475 0.1243394 m 0.5764444 0.1451879 l 0.6753859 0.1447677 l 0.6752889 0.1239515 l 0.5763475 0.1243394 l h W* n /Im3 Do Q q 0.2258667 0.1248889 m 0.1271838 0.1252444 l 0.1272808 0.1460929 l 0.2259313 0.1457051 l 0.2258667 0.1248889 l h 0.2077980 0.1366222 m 0.2077980 0.7008566 l 0.2286141 0.7008566 l 0.2286141 0.1366222 l 0.2077980 0.1366222 l h 0.4315717 0.1447677 m 0.4113374 0.1497455 l 0.5434747 0.6864727 l 0.5637091 0.6814949 l 0.4315717 0.1447677 l h 0.5881131 0.1249535 m 0.5881131 0.1458020 l 0.6840485 0.1458020 l 0.6840485 0.1249535 l 0.5881131 0.1249535 l h W n /Im4 Do Q Q Q endstream endobj 10 0 obj <>stream HyTSwoɞc [5, BHBK!aPVX=u:XKèZ\;v^N߽~w.MhaUZ>31[_& (DԬlK/jq'VOV?:OsRUzdWRab5? Ζ&VϳS7Trc3MQ]Ymc:B :Ŀ9ᝩ*UUZ<"2V[4ZLOMa?\⎽"?.KH6|zӷJ.Hǟy~Nϳ}Vdfc n~Y&+`;A4I d|(@zPZ@;=`=v0v <\$ x ^AD W P$@P>T !-dZP C; t @A/a<v}a1'X Mp'G}a|OY 48"BDH4)EH+ҍ "~rL"(*DQ)*E]a4zBgE#jB=0HIpp0MxJ$D1(%ˉ^Vq%],D"y"Hi$9@"m!#}FL&='dr%w{ȟ/_QXWJ%4R(cci+**FPvu? 6 Fs2hriStݓ.ҍu_џ0 7F4a`cfb|xn51)F]6{̤0]1̥& "rcIXrV+kuu5E4v}}Cq9JN')].uJ  wG x2^9{oƜchk`>b$eJ~ :Eb~,m,-Uݖ,Y¬*6X[ݱF=3뭷Y~dó Qti zf6~`{v.Ng#{}}c1X%6fmFN9NN8SΥ'g\\R]Z\t]\7u}&ps[6v_`) {Q5W=b _zžAe#``/VKPo !]#N}R|:|}n=/ȯo#JuW_ `$ 6+P-AܠԠUA' %8佐b8]+<q苰0C +_ XZ0nSPEUJ#JK#ʢi$aͷ**>2@ꨖОnu&kj6;k%G PApѳqM㽦5͊---SbhZKZO9uM/O\^W8i׹ĕ{̺]7Vھ]Y=&`͖5_ Ыbhו ۶^ Mw7n<< t|hӹ훩' ZL$h՛BdҞ@iءG&vVǥ8nRĩ7u\ЭD-u`ֲK³8%yhYѹJº;.! zpg_XQKFAǿ=ȼ:ɹ8ʷ6˶5̵5͵6ζ7ϸ9к<Ѿ?DINU\dlvۀ܊ݖޢ)߯6DScs 2F[p(@Xr4Pm8Ww)Km  endstream endobj 11 0 obj <>/Filter/FlateDecode/Height 48/Length 516/Matte[1.0 1.0 1.0]/Name/X/Subtype/Image/Type/XObject/Width 48>>stream H/qr8IBB*⠖hH$ h4R[E!!b ZK@\ĉZjlwykNSWK>Sn/d1 kf>|X4xCpkTGniwT޶H-d/ ֊1 2o#;ZF (UܛY3[V~t߆,NJhkob1ڵ-& *Vf +W+MdP_sƉe':?go; 2cd}Gn@}דM(T1>!k%_@}Q}edyr{wT)ZPf|ES<`cI./룻|IE.}:^/; *1ܕ_.LID_/=?~2z"-a2󼔂ZJj {~  endstream endobj 12 0 obj <>/Filter/DCTDecode/Height 48/ImageName/malt-run48.jpg/Intent/RelativeColorimetric/Length 4802/Name/malt-run48.jpg/SMask 11 0 R/Subtype/Image/Type/XObject/Width 48>>stream Adobed00     u!"1A2# QBa$3Rqb%C&4r 5'S6DTsEF7Gc(UVWdte)8fu*9:HIJXYZghijvwxyzm!1"AQ2aqB#Rb3 $Cr4%ScD&5T6Ed' sFtUeuV7)(GWf8vgwHXhx9IYiy*:JZjz ?vo[uNqv`~8py0;;Wd~:|Xd4NRT$M,M<hBC"D$(g* @h D\vqn<[-m]%7XlrIiqmRAmq2@ehEB9Tݭ۽]ݽE;6j{'f&-S۹l+r89ibyVZۆ[[NKx`X3iJ8g/w_O=]6f9uyvڕ=?vWˎј8lC[I! 5=@+͎Ḿ"{wTn8ҪFF0s.{iw,R}`om[ݦ.A *N %U~;3O]Ի[*s/׸S%n'rN/qchu4E0Ergw^HR,2(# $q}>`#nc.{j0OņrL.- $%׺?d}?}q{ `Ӝ7J7'.Пsu| >6Stx2͹v;bl:;cgc#2)]V rmۇ E,!C),PĪ{uͮ0>eLzZnؙYr5}s=U$z~K9 pm Q%Am*GkUT)-љb-_sh/p6.Au|wq"m|<[;\n<{h ֶoӹ!욄oİ*j(ðL[BsZpn@ QSǏY[Q>mL\e\X2PHG܏w^/j\U{VFB?_\&v?fK.dm|Sd)cR iU2o2TUKj>^pRݽ$~N4`y1q\fX[x9s?fyGp]nwMl#-m CIo/YJ*($XbScM; =+i{wG),p˸wnrX\D9rY Z Zjx=k#$h;ĕ5I+RO *a|~2s5^]9oR+ (y#V>YMs`tr|͛%nsEl ?g n*lOhdRv=D8,>JrSCRQ v(V㗡r'*@`\$0 W# 윿g͟t\][veؽ("ym$# xkyYB4ʀj34ygY\.[lf7N+3IU팖ӚWSWC7n+%X O1}p9i#e\jL^_R* Ov[I%Umnm}?/o~o<WqwM{=rucq^lnLzQeddI%%iiRZxbLCoAN*6 Y\˻Nۆw%ĀHoI6֑VDK!Uy$y1:Co}?ߵ}aueU6N/GM56[/c2VQ&G55`3Jfjh P: hU `` +BGz"Wln]عo/a y8;w{# RtA*<ѫ@N~Um51/Zbͦ!]WV=]eJQ[CoXtoʼDܖܹlٷ49r5 rYs_/MɷZƶ\\O&s*)|)4i31_Q2^`?lW?gնukٳm~;wyZ٤D7kw Y&H7 ҭ,׏ǻ^Mp~c[ܘթۂ_e.v5ZREK>b h+i%<K,ŝВsAEjbÜ.yRo mۭnYٷy6͞{=nrkhmRMQuutnC߶h`q[cmci8^6? iIv:y%䞢YfIt{ endstream endobj 13 0 obj <>/Filter/DCTDecode/Height 48/Intent/RelativeColorimetric/Length 2295/Name/X/Subtype/Image/Type/XObject/Width 48>>stream Adobed00     u!"1A2# QBa$3Rqb%C&4r 5'S6DTsEF7Gc(UVWdte)8fu*9:HIJXYZghijvwxyzm!1"AQ2aqB#Rb3 $Cr4%ScD&5T6Ed' sFtUeuV7)(GWf8vgwHXhx9IYiy*:JZjz ?ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ_9Yc;'jZ؉R\v]4cԆui>)\ų+[Xj@[_q$4tIFc 󓽱{ɳ]]E5JK?-AyomjuOxt[ ޖc{p'kiC4&rPVF% jr$bhhUXDU*<иsMڮԉX/ae:i F;_c6gܶ-q݊(/"cFki$EAZ鼸BI>C,1tͥX^]j(7;O^m#7|ӳͶmܿܐ} w7+eku܌z "$t KPWe Ԉmpij 4$e3n "XdpLѩky )du<<&UX[q }.~ksgV|'(oewʻ,ۼVWV4J;|qZs nlv?oQ})A<1EC>>Zg}L?+F41bM>nԿ~oa{]EC;He)ex]VBl(22GP VRء[^+\qrT0 hJ^ u_vN_Ϻo.r-;2^rOwY0뗿u endstream endobj 14 0 obj <>/Filter/DCTDecode/Height 48/Intent/RelativeColorimetric/Length 1310/Name/X/Subtype/Image/Type/XObject/Width 48>>stream Adobed00     u!"1A2# QBa$3Rqb%C&4r 5'S6DTsEF7Gc(UVWdte)8fu*9:HIJXYZghijvwxyzm!1"AQ2aqB#Rb3 $Cr4%ScD&5T6Ed' sFtUeuV7)(GWf8vgwHXhx9IYiy*:JZjz ?ߺ^׽ulG?=gQ9g?9[?{]ԙ׺u{{^KG"$`gFU!,"fI.)#'Z0VP:BA3!e' ҢRRDWYOuiumjt,1_yw=ݔ"ժn<HԸdCn_=u~{ߺ^OyBR9iPO>/Filter/DCTDecode/Height 48/Intent/RelativeColorimetric/Length 1204/Name/X/Subtype/Image/Type/XObject/Width 48>>stream Adobed00     u!"1A2# QBa$3Rqb%C&4r 5'S6DTsEF7Gc(UVWdte)8fu*9:HIJXYZghijvwxyzm!1"AQ2aqB#Rb3 $Cr4%ScD&5T6Ed' sFtUeuV7)(GWf8vgwHXhx9IYiy*:JZjz ?ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^λ1S?l5&FjyaeaEv̏@ahԹt9bٛxDq\HŨ-Z6X h* Y?xHnmÐ6.koݾX$u]t'OzKɯ\[eYe|fbVgXIww` ]-gip-mpCRPI ՈPYI>/Filter/DCTDecode/Height 48/Intent/RelativeColorimetric/Length 3228/Name/X/Subtype/Image/Type/XObject/Width 48>>stream Adobed00     u!"1A2# QBa$3Rqb%C&4r 5'S6DTsEF7Gc(UVWdte)8fu*9:HIJXYZghijvwxyzm!1"AQ2aqB#Rb3 $Cr4%ScD&5T6Ed' sFtUeuV7)(GWf8vgwHXhx9IYiy*:JZjz ?^(1, J^A[S ,M++:ƲNBQ )!CMX(yH Ab<苘9N-ǚb孾k7k ["x"n-H-&H̭HR'+5c3+CE_KU*Ĭ4pJ# I[[[NKx`X3iJ8g/RKvݯ-{ޒ[8,}]im.Ǖ-yrBwC2y|&W&$m0=RDi LqRbcII^E2$u"_\}%c :x҅>7rI łG]s7~s8d<;nw,ɷIyeCiWEii .K,keVh@d$bhfXhCYBVP P̅2GJJH<3^Ze=եմOgvRHVX_<"[+R{ig~׹?ؠ/¼$?/G9k>f9E.3כ9$LLeFzjcRkCP44#1J/2F\) 5+iCH^jH4f8I^rw?uoy6{Kh)^ g/9CMbRxinKa9onZ|Q(`dU**PD ]P$M KWiUڑ#x^`A,:Z\M29>gkf߷lŸA6EWrՌgkxuj.C6}`{Q > \Lf [}'}srVQJ]']m7 HF43VSrrc`RIIc$!cVFX6ܿ`YR%OLY--b@SeU`}V6{ngrK6Y|~ͻeuauu~_&==X79_6oϧ=EGhq }&nfʱUdrPHѳ#0SB l*2C.'/dGy|߷]Ki$p\[T%d䥛 IXYe2b]zy"z#G`Z5.]lͼ z{8$xbTg,j 4poY?xHnmÐ6.koݾX$u]> endobj 18 0 obj <>/Filter/FlateDecode/Height 48/Length 1333/Matte[1.0 1.0 1.0]/Name/X/SMask 22 0 R/Subtype/Image/Type/XObject/Width 48>>stream H]LZWp%@qu4CU`E>4\cd-PT9i&C,ƶbf묮(~t·uIM@_Vzrtۦ9"Qu5[ H ' rɵdub.>9wNHPv㊏z@ Ms)tra}}`y\pUU$cnc{.K|>Ŭ¸=⹼ 剉{s鏌R.\ORߙ3c`Jh4l6}x4{p=nQ`:X\E񨇸`yF>pQ-a"wxj0yHܾ>P><ƮP(11RT&ax.v955]_=t: {QQ|0Ҷm2D0pz|3ن 8_1  OYYMj*DK3d2w@JeBBWFH=1}z`BI/X %_.?]w;INNᇟKaacW<>O0ff\_a.(9zq}ZV9+0lSgg999"$qVvLRa}EDD|O3Ea;p}`No׊抌].'XvԳjge9JũSb>stream HVoEk'W[hȀZJ)vh!2&v"-ۭJozaRo\)8p? B\@'pB@B !ۙH &~cfޛyZ53WGv}o5 aSʮlox>_?tCUQv&-x;~;Yhzܶpi9 |m{Ԩ\$>mvhNþ}@ 0. #ѹ:q*Pj'KWgծVK@&{zb 9H3? ىumӶW!Tڢ߃n{̻E{bpH݀p;'dËScr3h:Vc`4}O7 a7#Lf/3"249=bfoJcekhpQy4O.77^ZVTb#/FBBķ"cdW#w Ҿ^Q{B GVc[W72G9u%[#W%oqZYE D\uqC4c?*趜^HH (X48ty F4_tx\F_;@^#3, Es4wRFC?(sS7nOe3tlfr{ׅx1R4'N>dDTH+p R0 N2}›~u Ӽ3 c&#U6aݡ=2CL 6Sh8d9uJ(KȠ 2'N}v:pDzɢ\$R<>fd'z/ )Sߗ!1*6 jTcAs|h=UpV nĈO!b\Y˸GM#.Q+)36x=!?;X1jR.uƔ`,kw2۹ķnQFxnC>?I|ۑEkVv me(-)^FDbn64~ ~0%zr_]^% ɈԞo8S# endstream endobj 20 0 obj <>stream HW}LSW?d6l9k9#MꦋZ':"̍23g6l&Kܖi B)9ByЊd=;CIJIUi n@0H(SO(iI&<}A'BRU 4^)Q:"}rA*`vJ#Na8/8v m8mx~|}~Aw5+~ؘ1WGKQd4 G8c!&[cҸGi59y삢.55 \dk\c(IVHz>,,$9&]QMtCF=YP0R5/j=l֠JZ:N,T U*dMɒ}A/K;ZgGYZ$ B'yvX 4韁nza"Y-<8[e|fo ;V㝽c{=#:Q1 \ '9F+ibAuC 7xEiY'5#귡&L%jϮi$2Mdi5#o9;{G8v*ބG>{e2AUmFKӷpp6ȴMO H*=;o&?ךNLc~ T74ZN,ie7LuL_lxeKLKĸ12Cڎ*zjB{j_;l d?kVCLr*^i2fBeYCѰ*ƽ=PO-Yj#eZZP$ C<㯂cOĮ*wY٪嬂oXcfQ;F\oaz([[v0/{_ 5@D5WmnE!uX;W ].+9D\iao"4#yV!LЎR& X!IV9mG;@UGyl:;`sc;p?:qrAEqn|\-@>@IT, gӞ '4qI?8w ^Ml 1s3N;Ǵ~b+N:o A'sh&b:.hoZ`˹ht[^oݏ d.>YѧKB=E (DQ˶cxj ڸEmDP\P"m_,l޸;v5fڽ ok2cDgW#9Sۀό%3rnm6bf_O%MuYP7yi>^pMV_SLfۖ =Vt/mhn>瑛Jwv3XS4COMFim2n#RlV#=8CJmO ι Ql I/Ul^? F:qsC}Q8!$0Ym PvkU({Nenn]%lqevq;w/aw>v8'tE}'wBCB(6^irv~1OB$1'{u uB}$E< `mvrpmv97+)%U6#<-Cc2ϖ»'B1 endstream endobj 21 0 obj [/ICCBased 23 0 R] endobj 22 0 obj <>/Filter/FlateDecode/Height 48/Length 501/Name/X/Subtype/Image/Type/XObject/Width 48>>stream H/A_$NnDN.\ Z@\$B$D z !n^!H$N\T""qjwng.w7f\]&m﷎j/Ͳ?@p0kH{)z@ğ6L/i`ɣǁ]JU>Zv%T[-O[FV~ۣ}/ӅJ@;_KƀQŹ_s_+]%vbi>py #yH0k屄GD9 <<op+o\fG壯^=tl<3OQU3;n )y܇J)ڲ-n`G&RzTX?{T ?G<(R+]g/H8VK /'ITx<P9#oMw cc,t'  endstream endobj 23 0 obj <>stream HyTSwoɞc [5, BHBK!aPVX=u:XKèZ\;v^N߽~w.MhaUZ>31[_& (DԬlK/jq'VOV?:OsRUzdWRab5? Ζ&VϳS7Trc3MQ]Ymc:B :Ŀ9ᝩ*UUZ<"2V[4ZLOMa?\⎽"?.KH6|zӷJ.Hǟy~Nϳ}Vdfc n~Y&+`;A4I d|(@zPZ@;=`=v0v <\$ x ^AD W P$@P>T !-dZP C; t @A/a<v}a1'X Mp'G}a|OY 48"BDH4)EH+ҍ "~rL"(*DQ)*E]a4zBgE#jB=0HIpp0MxJ$D1(%ˉ^Vq%],D"y"Hi$9@"m!#}FL&='dr%w{ȟ/_QXWJ%4R(cci+**FPvu? 6 Fs2hriStݓ.ҍu_џ0 7F4a`cfb|xn51)F]6{̤0]1̥& "rcIXrV+kuu5E4v}}Cq9JN')].uJ  wG x2^9{oƜchk`>b$eJ~ :Eb~,m,-Uݖ,Y¬*6X[ݱF=3뭷Y~dó Qti zf6~`{v.Ng#{}}c1X%6fmFN9NN8SΥ'g\\R]Z\t]\7u}&ps[6v_`) {Q5W=b _zžAe#``/VKPo !]#N}R|:|}n=/ȯo#JuW_ `$ 6+P-AܠԠUA' %8佐b8]+<q苰0C +_ XZ0nSPEUJ#JK#ʢi$aͷ**>2@ꨖОnu&kj6;k%G PApѳqM㽦5͊---SbhZKZO9uM/O\^W8i׹ĕ{̺]7Vھ]Y=&`͖5_ Ыbhו ۶^ Mw7n<< t|hӹ훩' ZL$h՛BdҞ@iءG&vVǥ8nRĩ7u\ЭD-u`ֲK³8%yhYѹJº;.! zpg_XQKFAǿ=ȼ:ɹ8ʷ6˶5̵5͵6ζ7ϸ9к<Ѿ?DINU\dlvۀ܊ݖޢ)߯6DScs 2F[p(@Xr4Pm8Ww)Km  endstream endobj 1 0 obj <> endobj 2 0 obj <>stream Adobe Photoshop CS5 Macintosh 2014-07-10T11:55:44+02:00 2014-07-11T10:16:03+02:00 2014-07-11T10:16:03+02:00 JPEG 48 48 /9j/7QAMQWRvYmVfQ00AAf/uAA5BZG9iZQBkgAAAAAH/2wCEAAwICAgJCAwJCQwRCwoLERUPDAwP FRgTExUTExgRDAwMDAwMEQwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwBDQsLDQ4NEA4OEBQO Dg4UFA4ODg4UEQwMDAwMEREMDAwMDAwRDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDP/AABEI ADAAMAMBIgACEQEDEQH/3QAEAAP/xAE/AAABBQEBAQEBAQAAAAAAAAADAAECBAUGBwgJCgsBAAEF AQEBAQEBAAAAAAAAAAEAAgMEBQYHCAkKCxAAAQQBAwIEAgUHBggFAwwzAQACEQMEIRIxBUFRYRMi cYEyBhSRobFCIyQVUsFiMzRygtFDByWSU/Dh8WNzNRaisoMmRJNUZEXCo3Q2F9JV4mXys4TD03Xj 80YnlKSFtJXE1OT0pbXF1eX1VmZ2hpamtsbW5vY3R1dnd4eXp7fH1+f3EQACAgECBAQDBAUGBwcG BTUBAAIRAyExEgRBUWFxIhMFMoGRFKGxQiPBUtHwMyRi4XKCkkNTFWNzNPElBhaisoMHJjXC0kST VKMXZEVVNnRl4vKzhMPTdePzRpSkhbSVxNTk9KW1xdXl9VZmdoaWprbG1ub2JzdHV2d3h5ent8f/ 2gAMAwEAAhEDEQA/APUMjKxsVgsybWUMJ2h1jgwE87dzyPBQx+oYGU8142TVe8DcW1va8gcbtrCf FYX1+/5Hp/8ADLf+ouXO/U3KON12lpLWsyWuqc5+nPvZs1HvfdUytMMqlTRy86cfNRw8I4ZcNy/v u59eetZnRbOm5mIRIfYLK3fRe2GSxy1enfWfo+diMyDkV4znD303vax7XfnN2vLd39dc7/jOZ6lP TWa+62wGOYhkrlI/FNMyJy7aNj4lzo5fDy0YwEskxklM7VAT4Yf92+xAhwDmmQdQRwQnWX9XM/Ez OmVNx3hz8cCq5ndr26HcP5X5i1FKzkEGiKf/0Ow+v3/I9P8A4Zb/ANRcuQqbZi42F1Sppc6q9wlw msOrLLqWu27fp7rPz/zF1/1+/wCR6f8Awy3/AKi5YWLjHJ+qGXAc52Nc29gbr/wVm/Q+xlLnvUch 6j5OPzcDLmc9bxwCcfPHOE3Q/wAYDq7aem2McHtJsexzSCCC2v3NP5zdr1xxdLnN/chvzgOd/wBJ y1eq9Sfm4fSsd+yMagtds+kCHupiz3O/wGNTZ/bWHiPNgtefzrCfwCZI6+auZP3jBn5n9HEMGHH/ ANUl70/+czw+uZvRus2ZeK788iys/Re2foPXq3ROuYPWsRuTivG6P0lJPvYe7XD/AL+vK+n9Azes 9Ytx6BDPUJssPDWkyvVuj9Hw+j4bcXFbEfTf+c4/vORxcVn923qOeOHgxga5eGPy/ucP6b//0ew+ v3/I9P8A4Zb/ANRcg/UymvK6Hl4j5Dbi5j3N52vaWe2Z/lLpMzBxM6n0Mupt1Uh213Yj85rh7mqO D07C6fWasOoVMcdxAJOv9suTeH1X4U1hy5+8yzEgxlj9vhfJbw+gWCwFj65DmuEEEe1zHA/RVXp3 8w7+sfyNXoP1w+rb+p24VOBQ2r1rXuy72NDdPa7fc5v0/wA/6S0cX6n9Boxasd2MLTWINhLgSTyT sc1R+2SfAJ+5Rh8MPKxmDky5fdka+WMPTHiTfVvEx8bpVTqmBr7hvtd3c4k8rVUKaa6KmU1N211g Na3wAU1M2H//2Q== application/pdf uuid:763cf946-8b3b-ff46-bb7f-86730efd8ce2 xmp.did:038011740720681188C6B0D5E8A0D526 xmp.did:058011740720681188C6B0D5E8A0D526 created xmp.iid:058011740720681188C6B0D5E8A0D526 2014-07-10T11:55:44+02:00 Adobe Photoshop CS5 Macintosh saved xmp.iid:E6C056BF0820681188C6B0D5E8A0D526 2014-07-11T10:16:03+02:00 Adobe Photoshop CS5 Macintosh / 3 sRGB IEC61966-2.1 Adobe Photoshop for Macintosh -- Image Conversion Plug-in endstream endobj 3 0 obj <> endobj xref 0 4 0000000000 65535 f 0000028505 00000 n 0000028556 00000 n 0000035701 00000 n trailer <<61014550B4474CC29B0DD19515D6CDDE>]>> startxref 116 %%EOF malt-0.5.2/src/malt/resources/icons/malt-run48.png000066400000000000000000000105051400455127600217520ustar00rootroot00000000000000PNG  IHDR00` pHYs   OiCCPPhotoshop ICC profilexڝSgTS=BKKoR RB&*! J!QEEȠQ, !{kּ> H3Q5 B.@ $pd!s#~<<+"x M0B\t8K@zB@F&S`cbP-`'{[! eDh;VEX0fK9-0IWfH  0Q){`##xFW<+*x<$9E[-qWW.(I+6aa@.y24x6_-"bbϫp@t~,/;m%h^ uf@Wp~<5j>{-]cK'Xto(hw?G%fIq^D$.Tʳ?D*A, `6B$BB dr`)B(Ͱ*`/@4Qhp.U=pa( Aa!ڈbX#!H$ ɈQ"K5H1RT UH=r9\F;2G1Q= C7F dt1r=6Ыhڏ>C03l0.B8, c˱" VcϱwE 6wB aAHXLXNH $4 7 Q'"K&b21XH,#/{C7$C2'ITFnR#,4H#dk9, +ȅ3![ b@qS(RjJ4e2AURݨT5ZBRQ4u9̓IKhhitݕNWGw Ljg(gwLӋT071oUX**| J&*/Tު UUT^S}FU3S ԖUPSSg;goT?~YYLOCQ_ cx,!k u5&|v*=9C3J3WRf?qtN (~))4L1e\kXHQG6EYAJ'\'GgSSݧ M=:.kDwn^Loy}/TmG X $ <5qo</QC]@Caaᄑ.ȽJtq]zۯ6iܟ4)Y3sCQ? 0k߬~OCOg#/c/Wװwa>>r><72Y_7ȷOo_C#dz%gA[z|!?:eAAA!h쐭!ΑiP~aa~ 'W?pX15wCsDDDޛg1O9-J5*>.j<74?.fYXXIlK9.*6nl {/]py.,:@LN8A*%w% yg"/6шC\*NH*Mz쑼5y$3,幄'L Lݛ:v m2=:1qB!Mggfvˬen/kY- BTZ(*geWf͉9+̳ې7ᒶKW-X潬j9(xoʿܔĹdff-[n ڴ VE/(ۻCɾUUMfeI?m]Nmq#׹=TR+Gw- 6 U#pDy  :v{vg/jBFS[b[O>zG499?rCd&ˮ/~јѡ򗓿m|x31^VwwO| (hSЧc3- cHRMz%u0`:o_FPLTEf3̙f3f3ffffff3f3333f333f3f3̙f3̙̙̙̙f̙3̙ffffff3f3333f333f3̙f3̙̙f3̙f3ff̙ffff3f33̙33f333̙f3ffffff3ffff̙fff3fffffff3ffffffffffff3fff3f3f3f3ff33f3ffffff3f3333f333333̙3f3333333f3333f3f3f3ff3f33f33333333f333333333f333f3̙f3f3ffffff3f3333f333f3R34RTSQRSPO&&DDWWמ㽽ރtRNSS%XIDATxڔKn0?IBWEHF]F;Yu#('EH#zD .$ۢ)l83?QOxlG:dBdBx^ Bޥ5ϋL-z$ ZhW1$N;h (ĔAFK0o *E1c6on4nW"vmoE^I+of-(Gf`%Xztob;`gCqD/D#bj$U%Q /% rD:<Ԕm;x`I.'xβq kP^$ H .-#6K$ަnܠ?($`>Gր)h"6#Wt5}5ߥ'``IhNzݚFրKMnpX҂^3KKnW@~bOTk KaO'< Q.ؕe C%.p\(~:RqcOU' SvitksQ;Jۅš_[ V^#mvI#JIENDB`malt-0.5.2/src/malt/sequence/000077500000000000000000000000001400455127600160135ustar00rootroot00000000000000malt-0.5.2/src/malt/sequence/Alphabet.java000066400000000000000000000120751400455127600204030ustar00rootroot00000000000000/* * Alphabet.java Copyright (C) 2020. Daniel H. Huson * * (Some files contain contributions from other authors, who are then mentioned separately.) * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * */ package malt.sequence; import malt.data.INormalizer; /** * Alphabet base class *

* Created by huson on 9/30/14. */ public class Alphabet implements INormalizer { protected final byte alphabetSize; protected final long[] letter2code; protected final byte[] letter2normalized; protected final byte[] code2letter; protected final int bitsPerLetter; protected final int unusedBits; protected final int lettersPerWord; protected final long letterMask; protected final byte undefinedLetterCode; protected final String definitionString; /** * constructor * * @param definitionString * @param undefinedLetter */ public Alphabet(String definitionString, byte undefinedLetter) { boolean isUndefinedContained = (definitionString.indexOf(undefinedLetter) != -1); this.definitionString = definitionString.replaceAll("\\[", "").replaceAll("]", "").replaceAll("\\s+", " "); String[] letterGroups = this.definitionString.split(" "); alphabetSize = (byte) (letterGroups.length + (isUndefinedContained ? 0 : 1)); { int bits = 1; while (!(Math.pow(2, bits) > alphabetSize)) { bits++; } bitsPerLetter = bits; } letterMask = (1L << bitsPerLetter) - 1; lettersPerWord = 64 / bitsPerLetter; unusedBits = 64 - lettersPerWord * bitsPerLetter; System.err.println("Alphabet: " + definitionString + " bits: " + bitsPerLetter); code2letter = new byte[alphabetSize + 1]; undefinedLetterCode = alphabetSize; letter2code = new long[127]; letter2normalized = new byte[127]; for (int i = 0; i < 127; i++) { letter2code[i] = undefinedLetterCode; letter2normalized[i] = undefinedLetter; } code2letter[undefinedLetterCode] = undefinedLetter; int bits = 1; for (String letterGroup : letterGroups) { for (int j = 0; j < letterGroup.length(); j++) { int letter = Character.toLowerCase(letterGroup.charAt(j)); letter2code[letter] = bits; letter = Character.toUpperCase(letterGroup.charAt(j)); letter2code[letter] = bits; letter2normalized[letter] = (byte) letterGroup.charAt(0); if (j == 0) code2letter[bits] = (byte) letter; } // System.err.println(letterGroups[i]+" -> "+Integer.toBinaryString(bits)+" -> "+(char)code2letter[bits]); bits++; } } /** * gets the alphabet size * * @return alphabet size */ public byte getAlphabetSize() { return alphabetSize; } /** * gets the number of bits used to encode a letter * * @return number of bits */ public int getBitsPerLetter() { return bitsPerLetter; } /** * gets the letter to code mapping * * @return letter to code */ public long[] getLetter2Code() { return letter2code; } /** * gets the code to letter mapping * * @return code to letter */ public byte[] getCode2Letter() { return code2letter; } /** * gets the mask used for a single letter * * @return letter mask */ public long getLetterMask() { return letterMask; } /** * gets the number of letters per 64-bit word * * @return letters per word */ public int getLettersPerWord() { return lettersPerWord; } /** * gets the number of unused bits * * @return number of unused (per 64-bit word) */ public int getUnusedBits() { return unusedBits; } /** * gets the code assigned to undefined letter * * @return code */ public byte getUndefinedLetterCode() { return undefinedLetterCode; } /** * gets the definition string * * @return defintion */ public String getDefinitionString() { return definitionString; } /** * returns normalized letter * * @param letter * @return normalized letter */ @Override public byte getNormalized(byte letter) { return letter2normalized[letter]; } } malt-0.5.2/src/malt/sequence/DNA5Alphabet.java000066400000000000000000000046731400455127600210200ustar00rootroot00000000000000/* * DNA5Alphabet.java Copyright (C) 2020. Daniel H. Huson * * (Some files contain contributions from other authors, who are then mentioned separately.) * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * */ package malt.sequence; /** * DNA5 alphabet * Created by huson on 9/30/14. */ public class DNA5Alphabet extends Alphabet { private static DNA5Alphabet instance; final static private byte[] normalizedComplement = { 'N', 'N', 'N', 'N', 'N', 'N', 'N', 'N', 'N', 'N', 'N', 'N', 'N', 'N', 'N', 'N', 'N', 'N', 'N', 'N', 'N', 'N', 'N', 'N', 'N', 'N', 'N', 'N', 'N', 'N', 'N', 'N', 'N', 'N', 'N', 'N', 'N', 'N', 'N', 'N', 'N', 'N', 'N', 'N', 'N', '-', 'N', 'N', 'N', 'N', 'N', 'N', 'N', 'N', 'N', 'N', 'N', 'N', 'N', 'N', 'N', 'N', 'N', 'N', 'N', 'T', 'N', 'G', 'N', 'N', 'N', 'C', 'N', 'N', 'N', 'N', 'N', 'N', 'N', 'N', 'N', 'N', 'N', 'N', 'A', 'A', 'N', 'N', 'N', 'N', 'N', 'N', 'N', 'N', 'N', 'N', 'N', 'T', 'N', 'G', 'N', 'N', 'N', 'C', 'N', 'N', 'N', 'N', 'N', 'N', 'N', 'N', 'N', 'N', 'N', 'N', 'A', 'A', 'N', 'N', 'N', 'N', 'N', 'N', 'N', 'N', 'N' }; /** * gets the single instance of the protein alphabet * * @return instance */ public static DNA5Alphabet getInstance() { if (instance == null) instance = new DNA5Alphabet(); return instance; } /** * constructor */ private DNA5Alphabet() { super("A C G TU", (byte) 'N'); } /** * gets the reverse complement * * @param sequence * @param reverseComplement */ public static void reverseComplement(byte[] sequence, byte[] reverseComplement) { for (int i = 0; i < sequence.length; i++) { reverseComplement[sequence.length - (i + 1)] = normalizedComplement[sequence[i]]; } } } malt-0.5.2/src/malt/sequence/FastAFileIteratorCode.java000066400000000000000000000204211400455127600227600ustar00rootroot00000000000000/* * FastAFileIteratorCode.java Copyright (C) 2020. Daniel H. Huson * * (Some files contain contributions from other authors, who are then mentioned separately.) * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * */ package malt.sequence; import jloda.util.Basic; import java.io.BufferedInputStream; import java.io.IOException; /** * Reads in a multifast file and places all headers in byte arrays and all sequences in encoded arrays * Daniel Huson, 8.2014 */ public class FastAFileIteratorCode { private final SequenceEncoder sequenceEncoder; private byte[] buffer = new byte[10000000]; private int length = 0; private long position = 0; private final long maxProgress; private boolean expectingHeader = true; private final BufferedInputStream inputStream; private boolean isFastQ = false; private boolean ok = true; // haven't seen next() fail yet private boolean nextIsLoaded = false; // have already loaded the next item /** * constructor * * @param fileName * @throws java.io.FileNotFoundException */ public FastAFileIteratorCode(final String fileName, final SequenceEncoder sequenceEncoder) throws IOException { this.sequenceEncoder = sequenceEncoder; inputStream = new BufferedInputStream(Basic.getInputStreamPossiblyZIPorGZIP(fileName), 8192); maxProgress = Basic.guessUncompressedSizeOfFile(fileName); try { int value = inputStream.read(); isFastQ = (value == '@'); } catch (IOException e) { } } /** * has next header or sequence * * @return true if has a header or sequence */ public boolean hasNext() { if (!ok) return false; else if (nextIsLoaded) return true; try { if (isFastQ) { // expect four lines per read try { length = 0; if (expectingHeader) { buffer[length++] = (byte) '>'; int value = inputStream.read(); if (value == -1) return ok = false; if (value != '@') buffer[length++] = (byte) value; length = readLineIntoBuffer(inputStream, length); position += length; return ok = (length > 1); } else { length = readLineIntoBuffer(inputStream, length); if (length == 0) return ok = false; position += length; position += skipLine(inputStream); // skip comment line position += skipLine(inputStream); // skip quality line return ok = true; } } catch (IOException e) { return ok = false; } } else { int value; length = 0; boolean first = true; try { while (true) { value = inputStream.read(); if (expectingHeader) { if (value == -1) return ok = false; if (first) { first = false; if (value != '>') buffer[length++] = '>'; } if (value == '\n' || value == '\r') { position += length; return ok = (length > 0); } } else { if (Character.isWhitespace(value)) continue; // skip white space if (value == '>' || value == -1) { position += length; return ok = (length > 0); } } if (length >= buffer.length) growBuffer(); buffer[length++] = (byte) value; } } catch (IOException e) { return ok = false; } } } finally { nextIsLoaded = true; } } /** * get next header * * @return header */ public byte[] nextHeader() { try { if (!nextIsLoaded && !hasNext()) return null; if (expectingHeader) expectingHeader = false; else nextSequenceCode(); // skip sequence if (length > 0 || hasNext()) { byte[] result = new byte[length]; System.arraycopy(buffer, 0, result, 0, length); length = 0; return result; } return null; } finally { nextIsLoaded = false; } } /** * get next sequence * * @return sequence */ public long[] nextSequenceCode() { try { if (!nextIsLoaded && !hasNext()) return null; if (expectingHeader) nextHeader(); // skip header else expectingHeader = true; if (length > 0 || hasNext()) { long[] result = sequenceEncoder.encode(buffer, length); length = 0; return result; } return null; } finally { nextIsLoaded = false; } } /** * read the next line into the buffer * * @param inputStream * @param offset * @return position of next available position in buffer */ private int readLineIntoBuffer(BufferedInputStream inputStream, int offset) throws IOException { int value = inputStream.read(); while (value != '\r' && value != '\n' && value != -1) { if (offset >= buffer.length) { // need to grow buffer growBuffer(); } buffer[offset++] = (byte) value; value = inputStream.read(); } return offset; } /** * grows the line buffer */ private void growBuffer() { byte[] nextBuffer = new byte[(int) Math.min(Integer.MAX_VALUE - 10L, 2 * buffer.length)]; System.arraycopy(buffer, 0, nextBuffer, 0, buffer.length); buffer = nextBuffer; } /** * skip the current line * * @param inputStream * @throws java.io.IOException */ private int skipLine(BufferedInputStream inputStream) throws IOException { int skipped = 0; int value = inputStream.read(); while (value != '\r' && value != '\n' && value != -1) { value = inputStream.read(); skipped++; } return skipped; } public void remove() { } /** * close the stream * * @throws java.io.IOException */ public void close() throws IOException { inputStream.close(); } /** * gets the maximum progress value * * @return maximum progress value */ public long getMaximumProgress() { return maxProgress; } /** * gets the current progress value * * @return current progress value */ public long getProgress() { return position; } /** * is the file we are reading actually a fastQ file? * * @return true, if fastQ */ public boolean isFastQ() { return isFastQ; } } malt-0.5.2/src/malt/sequence/ISeedExtractor.java000066400000000000000000000035211400455127600215440ustar00rootroot00000000000000/* * ISeedExtractor.java Copyright (C) 2020. Daniel H. Huson * * (Some files contain contributions from other authors, who are then mentioned separately.) * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * */ package malt.sequence; /** * seed extract * Daniel Huson, 2014 */ public interface ISeedExtractor { byte[] decodeSeed(long seedCode, int seedWeight); /** * extract a seed from a sequence code * * @param seedShape * @param seedWeight * @param sequenceCode * @param pos @return seed */ long getSeedCode(boolean[] seedShape, int seedWeight, long[] sequenceCode, int pos); /** * extract a seed from a sequence code * * @param seedShape * @param seedWeight * @param sequenceCode * @param pos * @param failValue value returned if sequence too short @return seed */ long getSeedCode(boolean[] seedShape, int seedWeight, long[] sequenceCode, int pos, int failValue); /** * is this a good seed? * * @param seedCode * @return true, if good */ boolean isGoodSeed(long seedCode, int seedWeight); /** * get the number of bits per letter * * @return bits */ int getBitsPerLetter(); } malt-0.5.2/src/malt/sequence/ProteinAlphabet.java000066400000000000000000000025701400455127600217430ustar00rootroot00000000000000/* * ProteinAlphabet.java Copyright (C) 2020. Daniel H. Huson * * (Some files contain contributions from other authors, who are then mentioned separately.) * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * */ package malt.sequence; /** * protein alphabet * Daniel Huson, 2014 */ public class ProteinAlphabet extends Alphabet { private static ProteinAlphabet instance; /** * gets the single instance of the protein alphabet * * @return instance */ public static ProteinAlphabet getInstance() { if (instance == null) instance = new ProteinAlphabet(); return instance; } /** * constructor */ private ProteinAlphabet() { super("A C D E F G H I K L M N P Q R S T V W Y", (byte) 'X'); } } malt-0.5.2/src/malt/sequence/ProteinSequenceEncoder.java000066400000000000000000000144361400455127600232770ustar00rootroot00000000000000/* * ProteinSequenceEncoder.java Copyright (C) 2020. Daniel H. Huson * * (Some files contain contributions from other authors, who are then mentioned separately.) * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * */ package malt.sequence; import jloda.util.Basic; import jloda.util.ProgressPercentage; import malt.data.SeedShape; import java.io.IOException; import java.util.Iterator; /** * test protein sequence encoder * Created by huson on 9/30/14. */ public class ProteinSequenceEncoder { /** * test program * * @param args * @throws java.io.IOException */ public static void main(String[] args) throws IOException { SequenceEncoder encoder = new SequenceEncoder(ProteinAlphabet.getInstance()); ReducedAlphabet reducedAlphabet = new ReducedAlphabet(ProteinAlphabet.getInstance(), "DIAMOND_11"); byte[] sequence1 = "MKTKSSNNIKKIYYISSILVGIYLCWQIIIQIIFLMDNSIAILEAIGMVVFISVYSLAVAINGWILVGRMKKSSKKAQYE".getBytes(); System.err.println("set: " + Basic.toString(sequence1)); long[] encoded = encoder.encode(sequence1, sequence1.length, null); byte[] sequence2 = encoder.decode(encoded); System.err.println("get: " + Basic.toString(sequence2)); System.err.println("SAME: " + Basic.equal(sequence1, sequence2)); for (int i = 0; i < sequence2.length; i++) { if (sequence2[i] != encoder.decode(encoder.getLetterCode(encoded, i))) System.err.println((char) sequence2[i] + " != " + (char) encoder.getLetterCode(encoded, i)); } System.err.print("It.: "); for (Iterator it = encoder.getLetterIterator(encoded); it.hasNext(); ) { System.err.print((char) (byte) it.next()); } System.err.println(); int skip = 30; System.err.print("It.: "); for (int i = 0; i < skip; i++) System.err.print(" "); for (Iterator it = encoder.getLetterIterator(encoded, skip); it.hasNext(); ) { System.err.print((char) (byte) it.next()); } System.err.println(); int rev = 30; System.err.print("It.: "); for (Iterator it = encoder.getLetterReverseIterator(encoded, rev); it.hasNext(); ) { System.err.print((char) (byte) it.next()); } System.err.println(" reverse " + rev); SeedShape2 seedShape = new SeedShape2(SeedShape.SINGLE_PROTEIN_SEED); System.err.println("SeedShape: " + seedShape); for (int i = 0; i < 5; i++) { System.err.print("Span at " + i + ": "); long[] span = encoder.getSeedSpanCode(seedShape.getLength(), encoded, i, null); System.err.print(Basic.toString(encoder.decode(span))); System.err.println(); long seedCode = encoder.getSeedCode(seedShape.getMask(), seedShape.getWeight(), encoded, i); System.err.println("Full seed at " + i + ": " + Basic.toString(encoder.decodeSeed(seedCode, seedShape.getWeight())) + " " + Basic.toBinaryString(seedCode)); long reducedSeedCode = reducedAlphabet.getSeedCode(seedShape.getMask(), seedShape.getWeight(), encoded, i); System.err.println("Reduced seed at " + i + ": " + Basic.toString(reducedAlphabet.decodeSeed(reducedSeedCode, seedShape.getWeight())) + " " + Basic.toBinaryString(reducedSeedCode)); } int limit = sequence1.length - seedShape.getLength(); long[] seeds = new long[2 * limit]; for (int i = 0; i < limit; i++) { seeds[2 * i] = reducedAlphabet.getSeedCode(seedShape.getMask(), seedShape.getWeight(), encoded, i); seeds[2 * i + 1] = i; } seeds = ProteinSequenceEncoder.radixSort2(seeds, seeds.length, 64 - reducedAlphabet.unusedBits, reducedAlphabet.bitsPerLetter, new ProgressPercentage("Sorting...")); for (int i = 0; i < seeds.length; i += 2) { System.err.printf("i=%3d pos=%3d seed=%s%n", i, seeds[i + 1], Basic.toString(reducedAlphabet.decodeSeed(seeds[i], seedShape.getWeight()))); } } /** * radix sort list of longs, using entries with even index as keys and entries with odd indices as associated values * * @param array * @param length * @param w number of bits to use (64 to sort full numbers) * @param d number of bits to consider at a time - in the case of 4-bit encoded letters: 4 * @return sorted array */ public static long[] radixSort2(long[] array, int length, int w, int d, final ProgressPercentage progress) { if (length % 2 != 0) throw new RuntimeException("radixSort2(length=" + length + "): length must be even"); final int steps = w / d; long[] a = array; long[] b = new long[length]; if (progress != null) { progress.setMaximum(steps); progress.setProgress(0); } for (int p = 0; p < steps; p++) { final int[] c = new int[1 << d]; // the next three for loops implement counting-sort for (int i = 0; i < length; i += 2) { c[(int) ((a[i] >> d * p) & ((1 << d) - 1))]++; } for (int i = 1; i < 1 << d; i++) c[i] += c[i - 1]; for (int i = length - 2; i >= 0; i -= 2) { final int index = (--c[(int) ((a[i] >> d * p) & ((1 << d) - 1))]) << 1; b[index] = a[i]; b[index + 1] = a[i + 1]; } // swap arrays final long[] tmp = b; b = a; a = tmp; if (progress != null) progress.setProgress(p); } return a; } } malt-0.5.2/src/malt/sequence/ReducedAlphabet.java000066400000000000000000000201331400455127600216710ustar00rootroot00000000000000/* * ReducedAlphabet.java Copyright (C) 2020. Daniel H. Huson * * (Some files contain contributions from other authors, who are then mentioned separately.) * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * */ package malt.sequence; import java.io.IOException; import java.util.Map; import java.util.TreeMap; /** * Reduced protein alphabet * Daniel Huson, 9.2014 */ public class ReducedAlphabet extends Alphabet implements ISeedExtractor { private final Alphabet proteinAlphabet; private final long[] proteinCode2ReducedCode; private static Map reductions; /** * constructor * * @param proteinAlphabet * @param reductionDefinition */ public ReducedAlphabet(final Alphabet proteinAlphabet, String reductionDefinition) throws IOException { super(getReductionDefinition(reductionDefinition), (byte) 'X'); this.proteinAlphabet = proteinAlphabet; proteinCode2ReducedCode = new long[proteinAlphabet.getCode2Letter().length]; for (int i = 1; i < proteinCode2ReducedCode.length; i++) { proteinCode2ReducedCode[i] = getLetter2Code()[proteinAlphabet.getCode2Letter()[i]]; /* System.err.println(Integer.toBinaryString(i)+" -> "+(char)proteinAlphabet.getCode2Letter()[i]+" -> " +Integer.toBinaryString((int)proteinCode2ReducedCode[i])); */ } } /** * decode a sequenceCode * * @param sequenceCode * @param bytes sequence * @return sequence length */ public int decode(long[] sequenceCode, byte[] bytes) { int shift = 64 - bitsPerLetter; int word = 0; int length = 0; while (true) { byte bits = (byte) ((sequenceCode[word] & (letterMask << shift)) >>> shift); if (bits == 0) break; bytes[length++] = code2letter[bits]; shift -= bitsPerLetter; if (shift < 0) { if (++word == sequenceCode.length) break; shift = 64 - bitsPerLetter; } } return length; } /** * decode a sequence * * @param seedCode * @param seedWeight * @return seed sequence */ @Override public byte[] decodeSeed(long seedCode, int seedWeight) { byte[] sequence = new byte[seedWeight]; decode(new long[]{seedCode << (64 - seedWeight * bitsPerLetter)}, sequence); return sequence; } /** * gets a seed code using the reduced alphabet * * @param seedShape * @param seedWeight * @param sequenceCode * @param pos * @return reduced code */ public long getSeedCode(final boolean[] seedShape, int seedWeight, long[] sequenceCode, int pos) { return getSeedCode(seedShape, seedWeight, sequenceCode, pos, 0); } /** * gets a seed code using the reduced alphabet * * @param seedShape * @param seedWeight * @param sequenceCode * @param pos * @param failValue value to return if seed extraction fails due to sequence being too short @return reduced code */ public long getSeedCode(final boolean[] seedShape, int seedWeight, long[] sequenceCode, int pos, int failValue) { // this code is a bit tricky: // we need to use protein alphabet encoding parameters to step through the sequence code // we need to use reduced protein alphabet encoding parameters to compute seed code long seed = 0; int seedShift = (seedWeight - 1) * bitsPerLetter; int word = pos / proteinAlphabet.lettersPerWord; int letterInWord = pos - proteinAlphabet.lettersPerWord * word; int shift = 64 - (letterInWord + 1) * proteinAlphabet.bitsPerLetter; for (boolean aSeedShape : seedShape) { if (aSeedShape) { long bits = (sequenceCode[word] & (proteinAlphabet.letterMask << shift)) >>> shift; if (bits == 0) return failValue; bits = proteinCode2ReducedCode[(int) bits]; seed |= (bits << seedShift); seedShift -= bitsPerLetter; } shift -= proteinAlphabet.bitsPerLetter; if (shift < 0) { shift = 64 - proteinAlphabet.bitsPerLetter; word++; if (word == sequenceCode.length) return failValue; } } return seed; } /** * get a reduction by name * * @param name * @return reduction definition string or null */ public static String getReductionDefinition(String name) throws IOException { if (reductions == null) reductions = initReductions(); String value = reductions.get(name); if (value == null) { if (name.split(" ").length > 1) return name; else throw new IOException("Unknown reduction: " + name); } return value; } /** * is this a good seed? Yes, if it contains at least three different letters and none is undefined * * @param seedCode * @return true, if good */ public boolean isGoodSeed(long seedCode, int seedWeight) { int shift = 0; byte a = 0; byte b = 0; byte c = 0; while (shift < 64) { byte bits = (byte) ((seedCode & (letterMask << shift)) >>> shift); if (bits == 0) break; else if (bits == undefinedLetterCode) return false; if (a == 0) a = bits; else if (bits != a && b == 0) b = bits; else if (bits != a && bits != b && c == 0) c = bits; shift += bitsPerLetter; } return c != 0; } /** * setup the set of all known reductions * * @return reductions */ private static Map initReductions() { Map reductions = new TreeMap<>(); // From: Bioinformatics. 2009 June 1; 25(11): 1356–1362. Published online 2009 April 7. doi: 10.1093/bioinformatics/btp164: reductions.put("GBMR4", "[ADKERNTSQ] [YFLIVMCWH*X] G P"); reductions.put("SDM12", "A D [KER] N [STQ] [YF] [LIVM*X] C W H G P"); reductions.put("HSDM17", "A D [KE] R N T S Q Y F [LIV*X] M C W H G P"); // Murphy, Lynne Reed and Wallqvist, Anders and Levy, Ronald M., 2000 : reductions.put("BLOSUM50_4", "[LVIMC*] [AGSTP] [FYW] [EDNQKRH]"); reductions.put("BLOSUM50_8", "[LVIMC*] [AG] [ST] P [FYW] [EDNQ] [KR] H"); reductions.put("BLOSUM50_10", "[LVIM*] C A G [ST] P [FYW] [EDNQ] [KR] H"); reductions.put("BLOSUM50_11", "[LVIM*] C A G S T P [FYW] [EDNQ] [KR] H"); // this was produced from BLOSUM50_10 by separating S and T reductions.put("BLOSUM50_15", "[LVIM*] C A G S T P [FY] W E D N Q [KR] H"); reductions.put("DIAMOND_11", "[KREDQN*] C G H [ILV] M F Y W P [STA]"); // DIAMOND default // produced especially for MALT: reductions.put("MALT_10", "[LVIM*X] C [AST] G P [WYF] [DEQ] N [RK] H"); // use these together to get good sensitivity: reductions.put("MALT_12A", " [LVMI*WYF] C [AST] G P D E Q N R K H"); reductions.put("MALT_12B", " [LVM*I] W Y F C A S T G P [DEQNRK] H"); reductions.put("MALT_12C", "[LVIM*] C [AST] G P [FY] [DE] W N Q [KR] H"); reductions.put("UNREDUCED", "A D K E R N T S Q Y F L I V M C W H G P *"); return reductions; } } malt-0.5.2/src/malt/sequence/SeedShape2.java000066400000000000000000000070621400455127600206060ustar00rootroot00000000000000/* * SeedShape2.java Copyright (C) 2020. Daniel H. Huson * * (Some files contain contributions from other authors, who are then mentioned separately.) * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * */ package malt.sequence; import java.io.IOException; /** * seed shape * Daniel Huson, 8.2014 */ public class SeedShape2 { private final boolean[] mask; private final String shape; private final int weight; private int jumpToFirstZero = -1; // Source for all seed patterns: Ilie et al. BMC Genomics 2011, 12:280 http://www.biomedcentral.com/1471-2164/12/280 public static final String SINGLE_DNA_SEED = "111110111011110110111111"; public static final String SINGLE_PROTEIN_SEED = "111101101110111"; public static final String[] PROTEIN_SEEDS = new String[]{"111101101110111", "1111000101011001111", "11101001001000100101111", "11101001000010100010100111"}; private int id; // id is 0,..,number of seed shapes-1 /** * constructor * * @param shape * @throws java.io.IOException */ public SeedShape2(String shape) throws IOException { this.shape = shape; mask = new boolean[shape.length()]; int ones = 0; for (int i = 0; i < shape.length(); i++) { if (shape.charAt(i) != '0') { mask[i] = true; ones++; } else { if (jumpToFirstZero == -1) jumpToFirstZero = i; } } weight = ones; } /** * string representation of shaped seed * * @return string */ public String toString() { return shape; } /** * get bytes * * @return string as bytes */ public byte[] getBytes() { return shape.getBytes(); } /** * length of spaced seed * * @return length */ public int getLength() { return mask.length; } /** * weight of spaced seed * * @return weight */ public int getWeight() { return weight; } /** * create correct size byte array for holding seed results * * @return bytes */ public byte[] createBuffer() { return new byte[getWeight()]; } /** * compute the number of positions to jump over to get to first 0 * * @return number of ones before first zero */ public int getJumpToFirstZero() { return jumpToFirstZero; } /** * gets the expected number of seeds * * @param numberOfSequences * @param numberOfLetters * @return expected number of seeds */ public long getMaxSeedCount(int numberOfSequences, long numberOfLetters, int numberOfJobs) { return Math.max(1, numberOfLetters - numberOfSequences * (weight - 1)) / numberOfJobs; } public void setId(int id) { this.id = id; } public int getId() { return id; } public boolean[] getMask() { return mask; } } malt-0.5.2/src/malt/sequence/SequenceEncoder.java000066400000000000000000000277621400455127600217440ustar00rootroot00000000000000/* * SequenceEncoder.java Copyright (C) 2020. Daniel H. Huson * * (Some files contain contributions from other authors, who are then mentioned separately.) * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * */ package malt.sequence; import java.util.Iterator; /** * sequence encoder * Daniel Huson, 2014 */ public class SequenceEncoder implements ISeedExtractor { protected final int bitsPerLetter; protected final int lettersPerWord; protected final long letterMask; protected final int unusedBits; protected final long[] letter2code; protected final byte[] code2letter; protected final byte undefinedLetterCode; /** * constructor * * @param alphabet */ public SequenceEncoder(final Alphabet alphabet) { bitsPerLetter = alphabet.getBitsPerLetter(); lettersPerWord = 64 / bitsPerLetter; letterMask = alphabet.getLetterMask(); unusedBits = alphabet.getUnusedBits(); letter2code = alphabet.getLetter2Code(); code2letter = alphabet.getCode2Letter(); undefinedLetterCode = alphabet.getUndefinedLetterCode(); } /** * encode a sequence * * @param sequence * @param length * @param sequenceCode array to use or null * @return encoded sequence */ public long[] encode(byte[] sequence, int length, long[] sequenceCode) { int numberOfWords = length / lettersPerWord + 1; if (sequenceCode == null || sequenceCode.length < numberOfWords) sequenceCode = new long[numberOfWords]; int shift = 64 - bitsPerLetter; int word = 0; for (int i = 0; i < length; i++) { sequenceCode[word] |= letter2code[sequence[i]] << shift; shift -= bitsPerLetter; if (shift < 0) { shift = 64 - bitsPerLetter; word++; } } /* for (int i = 0; i < numberOfWords; i++) { System.err.println(Long.toBinaryString(sequenceCode[i])); } */ return sequenceCode; } /** * encode a sequence * * @param sequence * @param length * @return encoded sequence */ public long[] encode(byte[] sequence, int length) { return encode(sequence, length, new long[length / lettersPerWord + 1]); } /** * encode a sequence * * @param sequence * @return encoded sequence */ public long[] encode(byte[] sequence) { return encode(sequence, sequence.length, new long[sequence.length / lettersPerWord + 1]); } /** * encode a single letter * * @param letter * @return code */ public byte encode(byte letter) { return (byte) letter2code[letter]; } /** * decode a sequenceCode * * @param sequenceCode * @param bytes sequence * @return sequence length */ public int decode(long[] sequenceCode, byte[] bytes) { int shift = 64 - bitsPerLetter; int word = 0; int length = 0; while (true) { byte bits = (byte) ((sequenceCode[word] & (letterMask << shift)) >>> shift); if (bits == 0) break; bytes[length++] = code2letter[bits]; shift -= bitsPerLetter; if (shift < 0) { if (++word == sequenceCode.length) break; shift = 64 - bitsPerLetter; } } return length; } /** * decode a sequence * * @param sequenceCode * @return sequence */ public byte[] decode(long[] sequenceCode) { byte[] sequence = new byte[computeLength(sequenceCode)]; decode(sequenceCode, sequence); return sequence; } /** * compute the length of the sequence. It is not stored explicitly. * * @param sequenceCode * @return sequence length */ public int computeLength(long[] sequenceCode) { int length = lettersPerWord * (sequenceCode.length - 1); // assume all but last word are full int shift = 64 - bitsPerLetter; final int word = sequenceCode.length - 1; while (shift >= 0) { byte bits = (byte) ((sequenceCode[word] & (letterMask << shift)) >>> shift); if (bits == 0) break; length++; shift -= bitsPerLetter; } return length; } /** * get a letter * * @param sequenceCode * @param pos * @return letter */ public byte getLetterCode(long[] sequenceCode, int pos) { int word = pos / lettersPerWord; int letterInWord = pos - lettersPerWord * word; int shift = 64 - (letterInWord + 1) * bitsPerLetter; return (byte) ((sequenceCode[word] & (letterMask << shift)) >>> shift); } /** * gets an getLetterCodeIterator over all letters * * @param sequenceCode * @return getLetterCodeIterator */ public Iterator getLetterIterator(final long[] sequenceCode) { return getLetterIterator(sequenceCode, 0); } /** * gets an iterator over all letters. * No check is performed to see whether pos is in range * * @param sequenceCode * @return iterator */ public Iterator getLetterIterator(final long[] sequenceCode, final int pos) { return new Iterator<>() { private int word = pos / lettersPerWord; private int shift = 64 - ((pos - lettersPerWord * word) + 1) * bitsPerLetter; byte bits = (byte) ((sequenceCode[word] & (letterMask << shift)) >>> shift); public boolean hasNext() { return bits > 0; } public Byte next() { byte result = decode(bits); // get next bits: shift -= bitsPerLetter; if (shift < 0) { word++; shift = 64 - bitsPerLetter; } if (word < sequenceCode.length) bits = (byte) ((sequenceCode[word] & (letterMask << shift)) >>> shift); else bits = 0; // else done return result; } public void remove() { } }; } /** * gets a reverse iterator over all letters. * No check is performed to see whether pos is in range * * @param sequenceCode * @return iterator */ public Iterator getLetterReverseIterator(final long[] sequenceCode, final int pos) { return new Iterator<>() { private int word = pos / lettersPerWord; private int shift = 64 - ((pos - lettersPerWord * word) + 1) * bitsPerLetter; byte bits = (byte) ((sequenceCode[word] & (letterMask << shift)) >>> shift); public boolean hasNext() { return bits > 0; } public Byte next() { byte result = decode(bits); shift += bitsPerLetter; if (shift >= 64) { shift = unusedBits; word--; } if (word >= 0) bits = (byte) ((sequenceCode[word] & (letterMask << shift)) >>> shift); else bits = 0; return result; } public void remove() { } }; } /** * gets a seed code using the reduced alphabet * * @param seedShape * @param seedWeight * @param sequenceCode * @param pos @return reduced code */ public long getSeedCode(final boolean[] seedShape, int seedWeight, long[] sequenceCode, int pos) { return getSeedCode(seedShape, seedWeight, sequenceCode, pos, 0); } /** * get the code for a given seed * * @param seedShape * @param seedWeight * @param sequenceCode * @param pos * @param failValue @return code */ public long getSeedCode(boolean[] seedShape, int seedWeight, long[] sequenceCode, int pos, int failValue) { long seed = 0; int seedShift = (seedWeight - 1) * bitsPerLetter; int word = pos / lettersPerWord; int letterInWord = pos - lettersPerWord * word; int shift = 64 - (letterInWord + 1) * bitsPerLetter; for (boolean aSeedShape : seedShape) { if (aSeedShape) { long bits = (sequenceCode[word] & (letterMask << shift)) >>> shift; if (bits == 0) return failValue; // System.err.println(Long.toBinaryString(bits)); seed |= (bits << seedShift); seedShift -= bitsPerLetter; } shift -= bitsPerLetter; if (shift < 0) { shift = 64 - bitsPerLetter; word++; if (word == sequenceCode.length) return failValue; } } return seed; } /** * decodes a seed code. For debugging only * * @param seedCode * @return bytes for seed */ public byte[] decodeSeed(long seedCode, int seedWeight) { return decode(new long[]{seedCode << (64 - seedWeight * bitsPerLetter)}); } /** * decodes a letter code * * @param letterCode * @return letter */ public byte decode(byte letterCode) { return code2letter[letterCode]; } /** * get the code for sequence spanned by a seed * * @param seedLength * @param sequenceCode * @param pos * @param seedWords * @return */ public long[] getSeedSpanCode(int seedLength, long[] sequenceCode, int pos, long[] seedWords) { if (seedWords == null) seedWords = new long[1 + seedLength / lettersPerWord]; int seedWord = 0; long seed = 0; int seedShift = 64 - bitsPerLetter; int word = pos / lettersPerWord; int letterInWord = pos - lettersPerWord * word; int shift = 64 - (letterInWord + 1) * bitsPerLetter; for (int i = 0; i < seedLength; i++) { long bits = (sequenceCode[word] & (letterMask << shift)) >>> shift; seed |= (bits << seedShift); seedShift -= bitsPerLetter; shift -= bitsPerLetter; if (shift < 0) { shift = 64 - bitsPerLetter; word++; } if (seedShift < 0) { seedShift = 64 - bitsPerLetter; seedWords[seedWord++] = seed; seed = 0; } } seedWords[seedWord] = seed; return seedWords; } /** * is this a good seed? Yes, if it contains at least two different letters and none is undefined * * @param seedCode * @return true, if good */ public boolean isGoodSeed(long seedCode, int seedWeight) { int shift = 0; byte a = 0; byte b = 0; while (shift < 64) { byte bits = (byte) ((seedCode & (letterMask << shift)) >>> shift); if (bits == 0) break; else if (bits == undefinedLetterCode) return false; if (a == 0) a = bits; else if (bits != a && b == 0) b = bits; shift += bitsPerLetter; } return b != 0; } public int getBitsPerLetter() { return bitsPerLetter; } } malt-0.5.2/src/malt/sequence/SequenceStore.java000066400000000000000000000165631400455127600214560ustar00rootroot00000000000000/* * SequenceStore.java Copyright (C) 2020. Daniel H. Huson * * (Some files contain contributions from other authors, who are then mentioned separately.) * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * */ package malt.sequence; import jloda.util.Basic; import jloda.util.CanceledException; import jloda.util.ProgressListener; import jloda.util.ProgressPercentage; import megan.io.OutputWriter; import java.io.DataInputStream; import java.io.File; import java.io.FileInputStream; import java.io.IOException; /** * Sequence store using encoded sequenceCodes * Created by huson on 10/1/14. */ public class SequenceStore { public final static int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8; private final SequenceEncoder sequenceEncoder; private int numberOfSequences; private byte[][] headers; private long[][] sequenceCodes; /** * constructor * * @param sequenceEncoder * @param size */ public SequenceStore(final SequenceEncoder sequenceEncoder, final int size) { this.sequenceEncoder = sequenceEncoder; headers = new byte[size][]; sequenceCodes = new long[size][]; } /** * read sequences from a fastA or fastQ file * * @param it * @param numberToRead * @return number of sequences read */ public int readFromFastA(FastAFileIteratorCode it, int numberToRead, ProgressListener progress) throws CanceledException { progress.setMaximum(numberToRead); progress.setProgress(0); numberOfSequences = 0; while (it.hasNext()) { if (numberOfSequences >= headers.length) { grow(); } headers[numberOfSequences] = it.nextHeader(); sequenceCodes[numberOfSequences] = it.nextSequenceCode(); numberOfSequences++; if (numberOfSequences >= numberToRead) break; progress.incrementProgress(); } return numberOfSequences; } /** * write to a file in binary format * * @param fileName */ public void write(final String fileName) throws IOException { try (OutputWriter outs = new OutputWriter(new File(fileName)); ProgressPercentage progress = new ProgressPercentage("Writing file: " + fileName, numberOfSequences)) { outs.writeInt(numberOfSequences); for (int i = 0; i < numberOfSequences; i++) { { int length = headers[i].length; outs.writeInt(length); outs.write(headers[i], 0, length); } { int length = sequenceCodes[i].length; outs.writeInt(length); for (int j = 0; j < length; j++) outs.writeLong(sequenceCodes[i][j]); } progress.incrementProgress(); } } } /** * read a file in binary format * * @param fileName * @return number of sequences read * @throws IOException */ public int read(final String fileName) throws IOException { DataInputStream ins = new DataInputStream(new FileInputStream(fileName)); numberOfSequences = ins.readInt(); if (headers.length < numberOfSequences) { // resize headers = new byte[numberOfSequences][]; sequenceCodes = new long[numberOfSequences][]; } ProgressPercentage progress = new ProgressPercentage("Reading file: " + fileName, numberOfSequences); for (int i = 0; i < numberOfSequences; i++) { { int length = ins.readInt(); for (int j = 0; j < length; j++) headers[i][j] = (byte) ins.read(); } { int length = ins.readInt(); for (int j = 0; j < length; j++) sequenceCodes[i][j] = ins.readLong(); } progress.incrementProgress(); } ins.close(); progress.close(); return numberOfSequences; } /** * get the number of sequences * * @return */ public int getNumberOfSequences() { return numberOfSequences; } /** * gets the i-th header * * @param i * @return header */ public byte[] getHeader(int i) { return headers[i]; } /** * gets the i-th sequence * * @param i * @return sequence */ public byte[] getSequence(int i) { return sequenceEncoder.decode(sequenceCodes[i]); } /** * gets the i-th sequence code * * @param i * @return sequence code */ public long[] getSequenceCode(int i) { return sequenceCodes[i]; } /** * gets the sequence encoder * * @return */ public SequenceEncoder getSequenceEncoder() { return sequenceEncoder; } /** * grow the data arrays */ private void grow() { final int newLength = (int) Math.min(MAX_ARRAY_SIZE, 2L * Math.max(16, headers.length)); System.err.print("[Grow: " + headers.length + " -> " + newLength + "]"); { byte[][] tmp = new byte[newLength][]; System.arraycopy(headers, 0, tmp, 0, headers.length); headers = tmp; } { long[][] tmp = new long[newLength][]; System.arraycopy(sequenceCodes, 0, tmp, 0, sequenceCodes.length); sequenceCodes = tmp; } } public static void main(String[] args) throws IOException { String fileName = "/Users/huson/data/megan/ecoli/x.fna"; SequenceStore sequenceStore = new SequenceStore(new SequenceEncoder(DNA5Alphabet.getInstance()), 2000); FastAFileIteratorCode fastAFileIteratorCode = new FastAFileIteratorCode(fileName, sequenceStore.getSequenceEncoder()); ProgressPercentage progress = new ProgressPercentage("Reading file: " + fileName); sequenceStore.readFromFastA(fastAFileIteratorCode, 2000, progress); progress.close(); System.err.println("Got:"); for (int i = 0; i < Math.min(5, sequenceStore.getNumberOfSequences()); i++) { System.err.println(Basic.toString(sequenceStore.getHeader(i))); System.err.println(Basic.toString(sequenceStore.getSequence(i))); } String binFile = "/Users/huson/tmp/x.idx"; sequenceStore.write(binFile); sequenceStore.read(binFile); System.err.println("Read: " + sequenceStore.numberOfSequences); System.err.println("Got:"); for (int i = 0; i < Math.min(5, sequenceStore.getNumberOfSequences()); i++) { System.err.println(Basic.toString(sequenceStore.getHeader(i))); System.err.println(Basic.toString(sequenceStore.getSequence(i))); } } } malt-0.5.2/src/malt/tools/000077500000000000000000000000001400455127600153435ustar00rootroot00000000000000malt-0.5.2/src/malt/tools/DumpReferencesFromMaltIndex.java000066400000000000000000000070511400455127600235520ustar00rootroot00000000000000/* * DumpReferencesFromMaltIndex.java Copyright (C) 2020. Daniel H. Huson * * (Some files contain contributions from other authors, who are then mentioned separately.) * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * */ package malt.tools; import jloda.swing.util.ArgsOptions; import jloda.util.*; import malt.MaltOptions; import malt.data.ReferencesDBAccess; import java.io.BufferedWriter; import java.io.File; import java.io.FileWriter; import java.io.IOException; public class DumpReferencesFromMaltIndex { /** * dump references from malt index */ public static void main(String[] args) { try { ProgramProperties.setProgramName("DumpReferencesFromMaltIndex"); ProgramProperties.setProgramVersion(megan.main.Version.SHORT_DESCRIPTION); PeakMemoryUsageMonitor.start(); (new DumpReferencesFromMaltIndex()).run(args); System.err.println("Total time: " + PeakMemoryUsageMonitor.getSecondsSinceStartString()); System.err.println("Peak memory: " + PeakMemoryUsageMonitor.getPeakUsageString()); System.exit(0); } catch (Exception ex) { Basic.caught(ex); System.exit(1); } } /** * run the program */ public void run(String[] args) throws IOException, UsageException { final ArgsOptions options = new ArgsOptions(args, this, "Dump references from MALT index"); options.setVersion(ProgramProperties.getProgramVersion()); options.setLicense("Copyright (C) 2020 Daniel H. Huson. This program comes with ABSOLUTELY NO WARRANTY."); options.setAuthors("Daniel H. Huson"); options.comment("Input Output"); final String indexDirectory = options.getOptionMandatory("-d", "index", "Index directory", ""); final String outputFile = options.getOptionMandatory("-o", "output", "Output file (.gz ok)", ""); options.comment(ArgsOptions.OTHER); final boolean headersOnly = options.getOption("-ho", "headersOnly", "Only save headers", false); options.done(); System.err.println("Loading references from: " + indexDirectory); final ReferencesDBAccess referencesDB = new ReferencesDBAccess(MaltOptions.MemoryMode.load, new File(indexDirectory, "ref.idx"), new File(indexDirectory, "ref.db"), new File(indexDirectory, "ref.inf")); int count = 0; try (BufferedWriter w = new BufferedWriter(new FileWriter(outputFile)); ProgressPercentage progress = new ProgressPercentage("Writing file: " + outputFile, referencesDB.getNumberOfSequences())) { for (int i = 0; i < referencesDB.getNumberOfSequences(); i++) { w.write(Basic.toString(referencesDB.getHeader(i)) + "\n"); if (!headersOnly) w.write(Basic.toString(referencesDB.getSequence(i)) + "\n"); progress.incrementProgress(); count++; } } System.err.printf("Lines: %,d%n", count); } } malt-0.5.2/src/malt/tools/RandomReadExtractor.java000066400000000000000000000130331400455127600221160ustar00rootroot00000000000000/* * RandomReadExtractor.java Copyright (C) 2020. Daniel H. Huson * * (Some files contain contributions from other authors, who are then mentioned separately.) * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * */ package malt.tools; import jloda.swing.util.ArgsOptions; import jloda.util.*; import java.io.*; import java.util.ArrayList; import java.util.Random; public class RandomReadExtractor { /** * convert feature tables to gene table */ public static void main(String[] args) throws Exception { try { ProgramProperties.setProgramName("RandomReadExtractor"); ProgramProperties.setProgramVersion(megan.main.Version.SHORT_DESCRIPTION); PeakMemoryUsageMonitor.start(); (new RandomReadExtractor()).run(args); System.err.println("Total time: " + PeakMemoryUsageMonitor.getSecondsSinceStartString()); System.err.println("Peak memory: " + PeakMemoryUsageMonitor.getPeakUsageString()); System.exit(0); } catch (Exception ex) { Basic.caught(ex); System.exit(1); } } /** * run the program */ public void run(String[] args) throws IOException, UsageException { final ArgsOptions options = new ArgsOptions(args, this, "Randomly cuts out reads from a single DNA sequence"); options.setVersion(ProgramProperties.getProgramVersion()); options.setLicense("Copyright (C) 2020 Daniel H. Huson. This program comes with ABSOLUTELY NO WARRANTY."); options.setAuthors("Daniel H. Huson"); options.comment("Input Output"); final String inputFile = options.getOptionMandatory("-i", "input", "FastA file containing a single sequence", ""); final String outputFile = options.getOptionMandatory("-o", "output", "Output file (.gz ok)", ""); options.comment("Options"); final String startsFile = options.getOption("-sf", "startsFile", "Select the reads from the list of start positions given in this file (overrides -n)", ""); final int numberOfReads = options.getOption("-n", "num", "Number of reads to extract", 10000); final int readLength = options.getOption("-l", "length", "Length of reads to extract", 100); final boolean forwardStrand = options.getOption("-fs", "forwardStrand", "From forward strand", true); final boolean backwardStrand = options.getOption("-bs", "backwardtrand", "From backward strand", true); final int randomSeed = options.getOption("-rs", "randomSeed", "Random number seed", 666); options.done(); final Random random = new Random(randomSeed); int count = 0; final FastA fastA = new FastA(); try (Reader r = new InputStreamReader(Basic.getInputStreamPossiblyZIPorGZIP(inputFile))) { fastA.read(r); System.err.printf("Sequence '%s' length: %,d%n", Basic.getFirstWord(fastA.getHeader(0)), fastA.getSequence(0).length()); } final String genome = fastA.getSequence(0); final ArrayList starts = new ArrayList<>(); if (startsFile.length() > 0) { try (FileLineIterator it = new FileLineIterator(startsFile, true)) { while (it.hasNext()) { String aLine = it.next(); if (Basic.isInteger(aLine)) starts.add(Basic.parseInt(aLine)); // either start or -start to indicate reverse complement } } } else { for (int i = 0; i < numberOfReads; i++) { final boolean forward = forwardStrand && !backwardStrand || (!backwardStrand || forwardStrand) && random.nextBoolean(); final int start = random.nextInt(genome.length() - readLength); if (forward) starts.add(start); else starts.add(-start); } } try (BufferedWriter w = new BufferedWriter(new FileWriter(outputFile)); ProgressPercentage progress = new ProgressPercentage("Writing file: " + outputFile, starts.size())) { for (int r = 0; r < starts.size(); r++) { final boolean forward = (starts.get(r) > 0); final int start = Math.abs(starts.get(r)); final int end = start + readLength; final String header = String.format(">r%06d %d-%d from %s", (r + 1), (start + 1), (end + 1), fastA.getHeader(0)); final String sequence; if (forward) { sequence = genome.substring(start, end); } else { sequence = SequenceUtils.getReverseComplement(genome.substring(start, end)); } w.write(header); w.write("\n"); w.write(sequence); w.write("\n"); progress.incrementProgress(); count++; } } System.err.printf("Lines: %,d%n", count); } } malt-0.5.2/src/malt/util/000077500000000000000000000000001400455127600151605ustar00rootroot00000000000000malt-0.5.2/src/malt/util/FixedSizePriorityQueue.java000066400000000000000000000066231400455127600224730ustar00rootroot00000000000000/* * FixedSizePriorityQueue.java Copyright (C) 2020. Daniel H. Huson * * (Some files contain contributions from other authors, who are then mentioned separately.) * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * */ package malt.util; import java.util.Comparator; import java.util.PriorityQueue; /** * A priority queue implementation with a fixed size */ public class FixedSizePriorityQueue { private final PriorityQueue priorityQueue; /* backing data structure */ private final Comparator comparator; private final int maxSize; /** * Constructs a {@link FixedSizePriorityQueue} with the specified {@code maxSize} * and {@code comparator}. * * @param maxSize - The maximum size the queue can reach, must be a positive integer. * @param comparator - The comparator to be used to compare the elements in the queue, must be non-null. */ public FixedSizePriorityQueue(final int maxSize, final Comparator comparator) { super(); if (maxSize <= 0) { throw new IllegalArgumentException("maxSize = " + maxSize + "; expected a positive integer."); } if (comparator == null) { throw new NullPointerException("Comparator is null."); } this.maxSize = maxSize; this.comparator = comparator; this.priorityQueue = new PriorityQueue<>(maxSize, comparator); } /** * Adds an element to the queue. If the queue contains {@code maxSize} elements, {@code e} will * be compared to the lowest element in the queue using {@code comparator}. * If {@code e} is greater than or equalOverShorterOfBoth to the lowest element, that element will be removed and * {@code e} will be added instead. Otherwise, the queue will not be modified * and {@code e} will not be added. * * @param e - Element to be added, must be non-null. * @return returns true if added */ public boolean add(final E e) { if (e == null) { throw new NullPointerException("e is null."); } if (priorityQueue.size() >= maxSize) { if (comparator.compare(e, priorityQueue.peek()) <= 0) return false; priorityQueue.poll(); // remove smallest element } return priorityQueue.add(e); } public int getMaxSize() { return maxSize; } public E poll() { return priorityQueue.poll(); } public int size() { return priorityQueue.size(); } public void clear() { priorityQueue.clear(); } public boolean remove(E entry) { return priorityQueue.remove(entry); } /** * get as collection * * @return collection */ public java.util.Collection getCollection() { return priorityQueue; } } malt-0.5.2/src/malt/util/TaxonomyUtilities.java000066400000000000000000000122151400455127600215360ustar00rootroot00000000000000/* * TaxonomyUtilities.java Copyright (C) 2020. Daniel H. Huson * * (Some files contain contributions from other authors, who are then mentioned separately.) * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * */ package malt.util; import jloda.graph.Node; import megan.classification.Classification; import megan.classification.ClassificationManager; import megan.viewer.TaxonomicLevels; import java.util.ArrayList; import java.util.List; /** * simple utilities * Daniel Huson, 8.2014 */ public class TaxonomyUtilities { /** * get the species for the taxon * * @param taxonId * @return species */ public static String getContainingSpecies(Integer taxonId) { final Classification classification = ClassificationManager.get(Classification.Taxonomy, false); Node v = classification.getFullTree().getTaxon2Node(taxonId); while (v != null) { taxonId = (Integer) v.getInfo(); int level = classification.getName2IdMap().getRank(taxonId); if (level == TaxonomicLevels.getSpeciesId()) return classification.getName2IdMap().get(taxonId); if (v.getInDegree() > 0) v = v.getFirstInEdge().getSource(); else v = null; } return null; } /** * get the genus * * @param taxonId * @return genus */ public static String getContainingGenus(Integer taxonId) { final Classification classification = ClassificationManager.get(Classification.Taxonomy, false); Node v = classification.getFullTree().getTaxon2Node(taxonId); while (v != null) { taxonId = (Integer) v.getInfo(); int level = classification.getName2IdMap().getRank(taxonId); if (level == TaxonomicLevels.getGenusId()) return classification.getName2IdMap().get(taxonId); if (v.getInDegree() > 0) v = v.getFirstInEdge().getSource(); else v = null; } return null; } /** * gets the strain * * @param taxonId * @return */ public static String getStrain(int taxonId) { final Classification classification = ClassificationManager.get(Classification.Taxonomy, false); final int speciesId = TaxonomicLevels.getSpeciesId(); final int subspeciesId = TaxonomicLevels.getSubspeciesId(); Node v = classification.getFullTree().getTaxon2Node(taxonId); while (v != null && v.getInDegree() > 0 && v.getInfo() != null) { v = v.getFirstInEdge().getSource(); taxonId = (Integer) v.getInfo(); String name = classification.getName2IdMap().get(taxonId); if (name != null && (name.equals("root") || name.equals("cellular organisms"))) break; int level = classification.getName2IdMap().getRank(taxonId); if (level > speciesId) break; if (level > subspeciesId) break; if (level != subspeciesId) break; } return null; } /** * gets the taxonomic path to the named taxon * * @param taxonId * @return */ public static String getPath(int taxonId) { final Classification classification = ClassificationManager.get(Classification.Taxonomy, false); final int genus = TaxonomicLevels.getGenusId(); if (taxonId == 1)// root taxon return classification.getName2IdMap().get(taxonId); List path = new ArrayList<>(); Node v = classification.getFullTree().getTaxon2Node(taxonId); while (v != null) { taxonId = (Integer) v.getInfo(); if (classification.getName2IdMap().getRank(taxonId) != 0) // ignore unranked nodes path.add(taxonId); if (v.getInDegree() > 0) v = v.getFirstInEdge().getSource(); else v = null; } StringBuilder buf = new StringBuilder(); if (path.size() > 0) { boolean isFirst = true; for (int i = path.size() - 1; i >= 0; i--) { if (isFirst) isFirst = false; else buf.append("; "); buf.append(classification.getName2IdMap().get(path.get(i))); } int level = classification.getName2IdMap().getRank(path.get(path.size() - 1)); if (level == genus) buf.append("."); } return buf.toString(); } } malt-0.5.2/src/malt/util/Tester.java000066400000000000000000000077641400455127600173070ustar00rootroot00000000000000/* * Tester.java Copyright (C) 2020. Daniel H. Huson * * (Some files contain contributions from other authors, who are then mentioned separately.) * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * */ package malt.util; /** * tester * Daniel Huson, 2014 */ public class Tester { /** * shows that modulo takes three times as long as bitwise-and or shift * * @param args */ public static void main(String[] args) { System.err.println("MAX: " + Integer.MAX_VALUE + " bin: " + Integer.toBinaryString(Integer.MAX_VALUE)); System.err.println("MIN+1: " + (Integer.MIN_VALUE + 1)); System.err.println("-MIN+1: " + (-(Integer.MIN_VALUE + 1)) + " equals MAX? " + (Integer.MAX_VALUE == (-(Integer.MIN_VALUE + 1)))); int hash = Integer.MAX_VALUE; System.err.print("hash: " + hash); if ((long) hash == Integer.MAX_VALUE || (long) hash <= Integer.MIN_VALUE + 1) hash = 0; System.err.println(" -> " + hash); hash = Integer.MIN_VALUE; System.err.print("hash: " + hash); if ((long) hash == Integer.MAX_VALUE || (long) hash <= Integer.MIN_VALUE + 1) hash = 0; System.err.println(" -> " + hash); hash = Integer.MIN_VALUE + 1; System.err.print("hash: " + hash); if ((long) hash == Integer.MAX_VALUE || (long) hash <= Integer.MIN_VALUE + 1) hash = 0; System.err.println(" -> " + hash); /* for(int i=0;i<32;i++) { System.err.println("i="+i+" (1<= Integer.MAX_VALUE) { tableSize = Integer.MAX_VALUE; } else { tableSize = 1; while (numberOfSeeds > tableSize) { tableSize *= 2; } } final int mask = tableSize - 1; System.err.println("tableSize: " + tableSize); System.err.println("mask: " + mask + " bits: " + Integer.toBinaryString(mask)); for (int i = tableSize - 5; i < tableSize + 5; i++) { System.err.println("i=" + i + " i&mask=" + (i & mask)); } for (int i = tableSize - 5; i < tableSize + 5; i++) { System.err.println("i=" + (-i) + " (-i)&mask=" + ((i) & mask)); } if (true) { int top = Integer.MAX_VALUE >>> 8; System.err.println("top: " + top); System.err.println("aMask: " + Integer.toBinaryString(top)); { long start = System.currentTimeMillis(); long sum = 0; for (int i = 0; i < Integer.MAX_VALUE; i++) { sum += (i % top); } System.err.println("Time: " + ((System.currentTimeMillis() - start) / 1000.0)); System.err.println("Sum: " + sum); } { long start = System.currentTimeMillis(); long sum = 0; for (int i = 0; i < Integer.MAX_VALUE; i++) { sum += (i & top); } System.err.println("Time: " + ((System.currentTimeMillis() - start) / 1000.0)); System.err.println("Sum: " + sum); } } System.exit(0); } } malt-0.5.2/src/malt/util/Utilities.java000066400000000000000000000315041400455127600200010ustar00rootroot00000000000000/* * Utilities.java Copyright (C) 2020. Daniel H. Huson * * (Some files contain contributions from other authors, who are then mentioned separately.) * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * */ package malt.util; import jloda.util.Basic; import jloda.util.BlastMode; import jloda.util.UsageException; import malt.data.ReadMatch; import malt.data.Row; import malt.data.SequenceType; import megan.classification.IdMapper; import megan.classification.commandtemplates.LoadMappingFileCommand; import megan.io.IIntPutter; import java.io.File; import java.io.IOException; import java.util.Random; /** * some utilities * Daniel Huson, 8.2014 */ public class Utilities { /** * randomize array of longs using (Durstenfeld 1964) * * @param array * @param offset start of numbers to be randomized * @param length number of numbers to be randomized * @param random */ public static void randomize(long[] array, int offset, int length, Random random) { for (int i = offset + length - 1; i >= offset + 1; i--) { int j = random.nextInt(i - offset) + offset; long tmp = array[i]; array[i] = array[j]; array[j] = tmp; } } /** * randomize array of integers using (Durstenfeld 1964) in consecutive pairs * * @param array * @param offset start of numbers to be randomized * @param length number of numbers to be randomized. Must be even for this to make sense * @param random */ public static void randomizePairs(int[] array, int offset, int length, Random random) { int end = offset + length / 2; for (int i = end - 1; i >= offset + 1; i--) { int j = random.nextInt(i - offset) + offset; int i2 = 2 * i - offset; int j2 = 2 * j - offset; int tmp = array[i2]; array[i2] = array[j2]; array[j2] = tmp; i2++; j2++; tmp = array[i2]; array[i2] = array[j2]; array[j2] = tmp; } } /** * randomize array of integers using (Durstenfeld 1964) in consecutive pairs * * @param array * @param offset start of numbers to be randomized * @param length number of numbers to be randomized. Must be even for this to make sense * @param random */ public static void randomizePairs(IIntPutter array, long offset, int length, Random random) { long end = offset + length / 2; for (long i = end - 1; i >= offset + 1; i--) { long j = random.nextInt((int) (i - offset)) + offset; long i2 = 2 * i - offset; long j2 = 2 * j - offset; int tmp = array.get(i2); array.put(i2, array.get(j2)); array.put(j2, tmp); i2++; j2++; tmp = array.get(i2); array.put(i2, array.get(j2)); array.put(j2, tmp); } } /** * resize array * * @param array * @return new array */ public static int[] resize(int[] array, int newSize) { int[] result = new int[newSize]; System.arraycopy(array, 0, result, 0, Math.min(newSize, array.length)); return result; } /** * resize array * * @param array * @return new array */ public static Row[] resizeAndConstructEntries(Row[] array, int newSize) { Row[] result = new Row[newSize]; for (int i = array.length; i < newSize; i++) result[i] = new Row(); System.arraycopy(array, 0, result, 0, Math.min(newSize, array.length)); return result; } /** * resize array * * @param array * @return new array */ public static ReadMatch[] resize(ReadMatch[] array, int newSize) { ReadMatch[] result = new ReadMatch[newSize]; System.arraycopy(array, 0, result, 0, Math.min(newSize, array.length)); return result; } /** * get first word of header * * @param header * @return first word */ public static byte[] getFirstWordSkipLeadingGreaterSign(byte[] header) { int start = 0; while (start < header.length) { byte ch = header[start]; if (ch != '>' && !Character.isWhitespace(ch)) break; start++; } int finish = start; while (finish < header.length) { byte ch = header[finish]; if (ch == 0 || Character.isWhitespace(ch)) break; finish++; } byte[] result = new byte[finish - start]; System.arraycopy(header, start, result, 0, finish - start); return result; } /** * get first word of header and make sure it starts with a greater sign * * @param header * @return first word */ public static byte[] getFirstWordEnsureLeadingGreaterSign(byte[] header) { int length = 0; boolean hasLeadingGreaterSign = (header.length > 0 && header[0] == '>'); while (length < header.length) { if (header[length] == 0 || Character.isWhitespace(header[length])) { if (hasLeadingGreaterSign) { byte[] result = new byte[length]; System.arraycopy(header, 0, result, 0, length); return result; } else { byte[] result = new byte[length + 1]; result[0] = '>'; System.arraycopy(header, 0, result, 1, length); return result; } } length++; } return header; } /** * copy a 0-terminated byte array * * @param bytes * @return copy (up to first 0) */ public static byte[] copy0Terminated(byte[] bytes) { int length = 0; while (length < bytes.length) { if (bytes[length] == 0) break; length++; } byte[] result = new byte[length]; System.arraycopy(bytes, 0, result, 0, length); return result; } /** * get first word of header and write it to result * * @param header * @return first word */ public static int getFirstWordSkipLeadingGreaterSign(byte[] header, byte[] result) { int start = (header.length > 0 && header[0] == '>' ? 1 : 0); while (start < header.length && Character.isWhitespace(header[start])) { start++; } if (start == header.length) return 0; int end = start; while (end < header.length && !Character.isWhitespace(header[end]) && header[end] != 0) { end++; } int length = Math.min(result.length, end - start); if (length > 0) System.arraycopy(header, start, result, 0, length); return length; } public static void checkFileExists(File file) throws IOException { checkFileExists(file.getPath(), false); } public static void checkFileExists(String fileName, boolean allowToAddGZSuffix) throws IOException { if ((new File(fileName)).exists()) return; // ok if (!allowToAddGZSuffix) { throw new IOException("File not found: " + fileName); } else if (!(new File(fileName + ".gz")).exists()) { throw new IOException("File not found: " + fileName + " nor " + fileName + ".gz"); } } /** * remove all existing index files * * @param indexDirectory */ public static void cleanIndexDirectory(File indexDirectory) throws IOException { if (!indexDirectory.isDirectory()) throw new IOException("Not a directory: " + indexDirectory); File[] files = indexDirectory.listFiles((file, s) -> s.endsWith(".idx") || s.contains(".idx.")); if (files != null) { System.err.println("Deleting index files: " + files.length); for (File file : files) if (!file.delete()) throw new IOException("Failed to delete file: " + file); } } /** * gets the query sequence type from the alignment program mode * * @param mode * @return query type * @throws UsageException */ public static SequenceType getQuerySequenceTypeFromMode(BlastMode mode) throws IOException { switch (mode) { case BlastN: case BlastX: return SequenceType.DNA; case BlastP: return SequenceType.Protein; default: throw new IOException("Unsupported mode: " + mode); } } /** * gets the reference sequence type from the alignment program mode * * @param mode * @return query type * @throws UsageException */ public static SequenceType getReferenceSequenceTypeFromMode(BlastMode mode) throws IOException { switch (mode) { case BlastN: return SequenceType.DNA; case BlastX: case BlastP: return SequenceType.Protein; default: throw new IOException("Unsupported mode: " + mode); } } /** * determines how many different frames are possible for a given query * * @param mode * @param dnaDoForward * @param dnaDoReverse * @return number of frames * @throws IOException */ public static int getMaxFramesPerQuery(BlastMode mode, boolean dnaDoForward, boolean dnaDoReverse) throws IOException { switch (mode) { case BlastN: return (dnaDoForward ? 1 : 0) + (dnaDoReverse ? 1 : 0); case BlastX: return 3 * ((dnaDoForward ? 1 : 0) + (dnaDoReverse ? 1 : 0)); case BlastP: return 1; default: throw new IOException("Unsupported mode: " + mode); } } /** * count the number of gaps ('-') in a sequence * * @param sequence * @return number of gaps */ public static int countGaps(byte[] sequence, int offset, int length) { int count = 0; for (int i = 0; i < length; i++) { int a = sequence[offset + i]; if (a == '-') count++; } return count; } /** * does this contain only at most two different letters * * @param seq * @return True at most two different letters occur */ public static boolean hasAtMostTwoLetters(byte[] seq) { byte a = seq[0]; byte b = 0; int pos = 1; while (pos < seq.length) { if (seq[pos] != a) { b = seq[pos]; break; } pos++; } while (pos < seq.length) { if (seq[pos] != a && seq[pos] != b) return false; pos++; } return true; } public static int getNextPowerOf2(int value) { long i = 1; for (; i < Integer.MAX_VALUE; i <<= 1) { if (i > value) return (int) i; } return Integer.MAX_VALUE; } /** * gets a file for a given directory with a given name, if it exists. If gzippedOk, also tries adding .gz or replacing the suffix by .gz * * @param directory * @param name * @param gzippedOk * @return file or null */ public static File getFile(String directory, String name, boolean gzippedOk) { File file = new File(directory, name); if (file.exists()) return file; if (gzippedOk) { file = new File(directory, name + ".gz"); if (file.exists()) return file; file = new File(directory, Basic.replaceFileSuffix(name, ".gz")); if (file.exists()) return file; } return null; } /** * load a mapping file * * @param fileName * @param mapType * @param cName * @throws Exception */ public static void loadMapping(String fileName, IdMapper.MapType mapType, String cName) throws Exception { if (fileName.length() > 0) (new LoadMappingFileCommand()).apply("load mapFile='" + fileName + "' mapType=" + mapType.toString() + " cName=" + cName + ";"); } } malt-0.5.2/src/module-info.java000066400000000000000000000002311400455127600163230ustar00rootroot00000000000000module malt { requires transitive jloda; requires transitive megan; exports malt.tools; exports malt; opens malt.resources.icons; }malt-0.5.2/tex/000077500000000000000000000000001400455127600132575ustar00rootroot00000000000000malt-0.5.2/tex/manual/000077500000000000000000000000001400455127600145345ustar00rootroot00000000000000malt-0.5.2/tex/manual/Makefile000066400000000000000000000003051400455127600161720ustar00rootroot00000000000000manual.pdf: manual.tex clean pdflatex manual bibtex manual pdflatex manual pdflatex manual makeindex manual pdflatex manual clean: rm -f manual.dvi manu.idx manual.aux manual.ps manual.pdf malt-0.5.2/tex/manual/definitions.tex000066400000000000000000000112161400455127600175720ustar00rootroot00000000000000% Show optional stuff: \newcommand{\optional}[1]{{#1}} % Hide optional stuff: \renewcommand{\optional}[1]{} % link stuff using hyperlinks: \newcommand{\link}[2]{\hyperlink{#2}{#1}} % a todo command: \newcommand{\todo}[1]{ \optional{\marginpar{\raggedright \tiny \textbf{todo:} {#1}}}} % use \mylabel instead of \label to see labels in print out \newcommand{\mylabel}[1]{ \optional{\marginpar{\raggedright\tiny{#1}}} \label{#1} \hypertarget{#1}{} } % use \target instead of \hypertarget to see targets in print out \newcommand{\target}[2]{ \hypertarget{#2}{#1} \optional{\marginpar{\raggedright\tiny{#2}}} } % use \mysection instead of \section to produce labels and hyperlink targets \newcommand{\mysection}[1]{\section{#1}\mylabel{sec:#1}} % use \mysubsection instead of \section to produce labels and hyperlink targets \newcommand{\mysubsection}[1]{\subsection{#1}\mylabel{subsec:#1}} \newcommand{\menu}[1]{ \link{{\tt #1}}{menu:#1}} \newcommand{\pmenu}[1]{ \target{{\tt #1}}{menu:#1} \index{#1}\hspace{-0.3cm} } \newcommand{\program}[1]{ \link{{\tt #1}}{program:#1}} \newcommand{\pprogram}[1]{ \target{{\tt #1}}{program:#1} \index{#1}\hspace{-0.3cm} } \newcommand{\windowmenu}[2]{ \link{{\tt $#2}}{windowmenu:#1-#2}\index{#2}\index{#2}} \newcommand{\pwindowmenu}[2] {\target{{\tt#2}}{windowmenu:#1-#2} \index{#2}\index{#2}} \newcommand{\submenu}[1]{ \link{{\tt #1}}{submenu:#1}} \newcommand{\psubmenu}[1]{ \target{{\tt #1}}{submenu:#1} \index{#1}} \newcommand{\menuitem}[2]{ \link{{\tt #1$\to$#2}}{menuitem:#1-#2} \index{#1$\to$#2}\index{#2}} \newcommand{\pmenuitem}[2]{\index{#1$\to$#2}\index{#2} \target{{\tt #1$\to$#2}}{menuitem:#1-#2}\hspace{-0.3cm} } \newcommand{\menuitemh}[3]{ \link{{\tt #1$\to$#2$\to$#3}}{menuitem:#1-#2-#3}\index{#1$\to$#2$\to$#3}\index{#3}} \newcommand{\pmenuitemh}[3] {\target{{\tt#1$\to$#2$\to$#3}}{menuitem:#1-#2-#3} \index{#1$\to$#2$\to$#3}\index{#3}} \newcommand{\windowmenuitem}[3]{ \link{{\tt #2$\to$#3}}{windowmenuitem:#1-#2-#3}\index{#2$\to$#3}\index{#3}} \newcommand{\pwindowmenuitem}[3] {\target{{\tt#2$\to$#3}}{windowmenuitem:#1-#2-#3} \index{#2$\to$#3}\index{#3}} \newcommand{\ppopupmenu}[1]{ \target{{\tt #1}}{popupmenu:#1} \index{#1}} \newcommand{\popupmenu}[1]{ \link{{\tt #1}}{popupmenu:#1} \index{#1}} \newcommand{\ppopupmenuitem}[2]{ \target{{\tt #2}}{popupmenuitem:#1-#2} \index{#1$\to$#2}\index{#2}} \newcommand{\popupmenuitem}[2]{ \link{{\tt #2}}{popupmenuitem:#1-#2} \index{#1$\to$#2}\index{#2}} \newcommand{\block}[1]{ \link{{\tt #1}}{block:#1}} \newcommand{\pblock}[1]{ \target{{\tt #1}}{block:#1} \index{#1}} \newcommand{\button}[1]{ \link{{\tt #1}}{button:#1}} \newcommand{\pbutton}[1]{\hspace{-0.3cm} \target{{\tt #1}}{button:#1} \index{#1}\hspace{-0.3cm}} \newcommand{\method}[1]{ \link{{\tt #1}}{method:#1}} \newcommand{\pmethod}[1]{ \target{{\tt #1}}{method:#1} \index{#1}} \newcommand{\window}[1]{ \link{{\tt #1}}{window:#1}} \newcommand{\pwindow}[1]{\hspace{-0.3cm} \target{{\tt #1}}{window:#1} \index{#1}\hspace{-0.3cm}} \newcommand{\tab}[2]{ \link{{\tt #1:#2}}{tab:#1-#2}} \newcommand{\ptab}[2]{ \target{{\tt #1:#2}}{tab:#1-#2} \index{#1:#2}} \newcommand{\tabtab}[3]{ \link{{\tt #1:#2:#3}}{tabtab:#1-#2-#3}} \newcommand{\ptabtab}[3]{ \target{{\tt #1:#2:#3}}{tabtab:#1-#2-#3} \index{#1:#2:#3}} \newcommand{\concept}[1]{ \link{{#1}}{concept:#1}} \newcommand{\pconcept}[1]{\hspace{-0.3cm} \target{{\em #1}}{concept:#1}\index{#1}\hspace{-0.2cm}} %%% The following commands are to help make hyperlinks: % use this to emphasize a word and to put it into the index: \newcommand{\iem}[1]{{\em #1}\index{#1}} \newcommand{\irm}[1]{{#1}\index{#1}} \newcommand{\itt}[1]{{\tt #1}\index{#1}} \newcommand{\ibf}[1]{{\bf #1}\index{#1}} \newcommand{\iit}[1]{{\it #1}\index{#1}} \newcommand{\isc}[1]{{\sc #1}\index{#1}} % use this to emphasize a word and to put it into the index and link it % to it's primary occurrence: \newcommand{\ieml}[1]{\link{{\em #1}}{#1}\index{#1}} \newcommand{\irml}[1]{\link{#1}{#1}\index{#1}} \newcommand{\ittl}[1]{\link{{\tt #1}}{#1}\index{#1}} \newcommand{\iitl}[1]{\link{{\it #1}}{#1}\index{#1}} \newcommand{\iscl}[1]{\link{{\sc #1}}{#1}\index{#1}} \newcommand{\il}[1]{\link{#1}{#1}\index{#1}} % use this to emphasize the primary occurrence of % word and to put it into the index, also make this occurrence of the word % a hyperlink target: \newcommand{\pem}[1]{{\em \hypertarget{#1}{#1}}\index{#1} \optional{{\marginpar{\raggedright\tiny{#1}}}}} % use this to ignore stuff: \newcommand{\ignore}[1]{} % some definitions: \def\kb{{\rm kb }} \def\bp{{\rm bp }} \def\Gb{{\rm Gb }} \def\Mb{{\rm Mb }} \def\this{.} % Headings %\pagestyle{myheadings} %\markboth{}{$ $Date: 2006-04-26 19:12:01 $ $\hfil User Manual \Megan v4b25} malt-0.5.2/tex/manual/manual.bbl000066400000000000000000000111371400455127600164750ustar00rootroot00000000000000\begin{thebibliography}{10} \bibitem{GeneOntology2000} M.~Ashburner, C.~A. Ball, J.~A. Blake, D.~Botstein, H.~Butler, J.~M. Cherry, A.~P. Davis, K.~Dolinski, S.~S. Dwight, J.~T. Eppig, M.~A. Harris, D.~P. Hill, L.~Issel-Tarver, A.~Kasarskis, S.~Lewis, J.~C. Matese, J.~E. Richardson, M.~Ringwald, G.~M. Rubin, and G.~Sherlock. \newblock Gene ontology: tool for the unification of biology. the gene ontology consortium. \newblock {\em Nat Genet}, 25(1):25--29, May 2000. \bibitem{Burkhardt01} Stefan Burkhardt and Juha K{\"a}rkk{\"a}inen. \newblock Better filtering with gapped q-grams. \newblock {\em Fundamenta Informaticae}, XXIII:1001--1018, 2001. \bibitem{ChaoPM92} Kun-Mao Chao, William~R. Pearson, and Webb Miller. \newblock Aligning two sequences within a specified diagonal band. \newblock {\em Computer Applications in the Biosciences}, 8(5):481--487, 1992. \bibitem{MEGAN2007} D.~H. Huson, A.~F. Auch, J.~Qi, and S.~C. Schuster. \newblock {MEGAN} analysis of metagenomic data. \newblock {\em Genome Res}, 17(3):377--386, March 2007. \bibitem{MEGAN2011} D.~H. Huson, S.~Mitra, N.~Weber, H.-J. Ruscheweyh, and S.~C. Schuster. \newblock Integrative analysis of environmental sequences using {MEGAN\,4}. \newblock {\em Genome Research}, 21:1552--1560, 2011. \bibitem{Ilie:2011fk} Lucian Ilie, Silvana Ilie, Shima Khoshraftar, and Anahita~Mansouri Bigvand. \newblock Seeds for effective oligonucleotide design. \newblock {\em BMC Genomics}, 12:280, 2011. \bibitem{Kanehisa2000} M.~Kanehisa and S.~Goto. \newblock {KEGG}: {Kyoto} encyclopedia of genes and genomes. \newblock {\em Nucleic Acids Res}, 28(1):27--30, Jan 2000. \bibitem{Ma02} Bin Ma, John Tromp, and Ming Li. \newblock {PatternHunter}: faster and more sensitive homology search. \newblock {\em Bioinformatics}, 18(3):440--445, 2002. \bibitem{Mitchell2015} Alex Mitchell, Hsin-Yu Chang, Louise Daugherty, Matthew Fraser, Sarah Hunter, Rodrigo Lopez, Craig McAnulla, Conor McMenamin, Gift Nuka, Sebastien Pesseat, Amaia Sangrador-Vegas, Maxim Scheremetjew, Claudia Rato, Siew-Yit Yong, Alex Bateman, Marco Punta, Teresa~K. Attwood, Christian~J.A. Sigrist, Nicole Redaschi, Catherine Rivoire, Ioannis Xenarios, Daniel Kahn, Dominique Guyot, Peer Bork, Ivica Letunic, Julian Gough, Matt Oates, Daniel Haft, Hongzhan Huang, Darren~A. Natale, Cathy~H. Wu, Christine Orengo, Ian Sillitoe, Huaiyu Mi, Paul~D. Thomas, and Robert~D. Finn. \newblock The interpro protein families database: the classification resource after 15 years. \newblock {\em Nucleic Acids Research}, 43(D1):D213--D221, 2015. \bibitem{Murphy2000} Lynne~Reed Murphy, Anders Wallqvist, and Ronald~M. Levy. \newblock Simplified amino acid alphabets for protein fold recognition and implications for folding. \newblock {\em Protein Engineering}, 13:149--152(4), 2000. \bibitem{SSAHA} Z.~Ning, A.~J. Cox, and J.~C. Mullikin. \newblock {SSAHA}: a fast search method for large {DNA} databases. \newblock {\em Genome Res}, 11(10):1725--1729, 2001. \bibitem{SEED2005} Ross Overbeek, Tadhg Begley, Ralph~M Butler, Jomuna~V Choudhuri, Han-Yu Chuang, Matthew Cohoon, Val\'erie de~Cr\'ecy-Lagard, Naryttza Diaz, Terry Disz, Robert Edwards, Michael Fonstein, Ed~D Frank, Svetlana Gerdes, Elizabeth~M Glass, Alexander Goesmann, Andrew Hanson, Dirk Iwata-Reuyl, Roy Jensen, Neema Jamshidi, Lutz Krause, Michael Kubal, Niels Larsen, Burkhard Linke, Alice~C McHardy, Folker Meyer, Heiko Neuweger, Gary Olsen, Robert Olson, Andrei Osterman, Vasiliy Portnoy, Gordon~D Pusch, Dmitry~A Rodionov, Christian R\"uckert, Jason Steiner, Rick Stevens, Ines Thiele, Olga Vassieva, Yuzhen Ye, Olga Zagnitko, and Veronika Vonstein. \newblock The subsystems approach to genome annotation and its use in the project to annotate 1000 genomes. \newblock {\em Nucleic Acids Res}, 33(17):5691--5702, 2005. \bibitem{eggNOG} Sean Powell, Damian Szklarczyk, Kalliopi Trachana, Alexander Roth, Michael Kuhn, Jean Muller, Roland Arnold, Thomas Rattei, Ivica Letunic, Tobias Doerks, Lars~Juhl Jensen, Christian von Mering, and Peer Bork. \newblock {eggNOG} v3.0: orthologous groups covering 1133 organisms at 41 different taxonomic ranges. \newblock {\em Nucleic Acids Research}, 40(Database-Issue):284--289, 2012. \bibitem{Tatusov1997} R.~L. Tatusov, E.~V. Koonin, and D.~J. Lipman. \newblock A genomic perspective on protein families. \newblock {\em Science}, 278(5338):631--637, Oct 1997. \bibitem{RapSearch2} Yongan Zhao, Haixu Tang, and Yuzhen Ye. \newblock {RAPSearch2}: a fast and memory-efficient protein similarity search tool for next-generation sequencing data. \newblock {\em Bioinformatics}, 28(1):125--126, 2012. \end{thebibliography} malt-0.5.2/tex/manual/manual.pdf000066400000000000000000006156211400455127600165170ustar00rootroot00000000000000%PDF-1.5 % 45 0 obj << /Length 480 /Filter /FlateDecode >> stream xڕn0E .ib9×IРEttD+@Y~}IQV*r)=+';fg0 CP[90. Z1)J@]g(z뚵ڻ:}ogq\TP3̨b8,`V) @0I&$VP+WCþ -¦}NU7?Ʉ#aHCO!VX9ln `ˬFx^3&L$ ͔!, HvfYnQYT:YxIZO]YeLﺪJ.ε/be0_;W55epCz:~tZ{C*疪Nt,yTMǾKߞ䯱4׸)$hַyOiޟX3!jvOr !Is*t| Rͧbc endstream endobj 68 0 obj << /Length 2912 /Filter /FlateDecode >> stream xڥYKs8ϯP퉪hoΞxvR\dIH I/d=5h4~} y̛œ/l<725[nf?KBߝbmXƬz~" Cm}sjnN7_A|"-(.:0%?=uKBnJ]bz=w8rc֛?18>ĵјhX;#2;*nttr#qw4p9 ^Έ>2BE1i?84-2!.8:ck/-mCPp}l[ A P0XΈv~ݗgj0?K3 ;0jԹt \%VI,5cý5fӒ;W_xspu #y(#IUqCYzDSswԷ`MT՛ۛJw7G \1yQ-4'kqG2C^6nxHژDZe_7"$7.vq_:-\fs{X|LiL͟8EV=6#@xZՖ^+ [bpgQ;7{N\5wཙ5?j<0g ~uNeH9u98!nz?-E?M&/SB/Α1(p*dܴ 2 9Qhn֦yR5q;`Bbkm̔`pȣ:p4\,O<+~9K¤u|@8[Sb HԹ\b-$Rz.Ƀ/1D$R*̲qS?x6 i&5%b; }14oZv L|7D_@2 gFCGm-6a2Tq#]y1 sA LgYsR F0H FN#$S#E%N EoW s)zvm@;YIG{ c ӘidE_VzȄ3-\Bڛ۫=>j 6B-b)'FFAaha/3܈~xnC|:Ӈ&BV, b\ߒq>RLLX&_ &p_ng~I8Sq%·Mʦ8 76WmWtN ` HrMע)dD<-EIAѽ*1g=!|6 3 zEY7-%r0gr<͠>3]{-^\2jpE: xjKJS,KM;!HxÌ!m٭ BjFb EC < |wlSD^h@l~UAp!O6bl< )RiX*H:66NJZ M.u%i4;S..N,%LlK2Ө֚_O2Kq xuDŽ B0PGi;S8ކȏa? #ӀLo"_eF|u#c7P[x`˔48W*P2G _L lUm1<ZyO;p8` |U}JT|0TVEYا YDv?6~CT?՝x+Π:]722zySFrGqoJ5gw0`p% N=~8P(]n}ݢdPL``j:`.!d ]ZUN{8W4遁K*|^˕za 6[j 3wLA;J4r9_j^0~#G܀=M?GNzy@B(_+^Tq?u-VǢ{Z2_>bͱR! = on3)[1Pnx3:o?5*}s#ݴ9i l\CM"zj (NT*t<{}$Z7bU7Kl_i'+c EId_h O,0;Ѐq{o8,PLo<975\ pw#kiG[г&NA4菼cRA/! R(D5\ wJCc|kʾS E>:0BZ DrTeŗT|cۘMwbM䜋H]vcťR4+ ,V5FA%˿_a"< u'SVRYI sO柱\/b* endstream endobj 79 0 obj << /Length 1903 /Filter /FlateDecode >> stream xڭXKoFWR*v_|mm P aME"iTwfgH2@.>fwJly}%r+m3vM"PZoUؘ`_ޡGzӨ+x\˝Jmw:˂ mӻ4T{rWYlCqxoHJ/?B *X/IF&# QUdůࠄI4:a4!R} q8JDp 1AA2;Vw\ PT+L,a~bgpOzwrM>zNq?whRF5hM5:E+T@ÑeMlSR]#b\:]5@?@J k61S֐)ǻpbaGIӺ 0 zw7Daz@v0 /~|Y,>=GLo;TmC,ȼ?Uay{ʻO{Ե`Ep߽2ӌBmbkTnɠxZF~P@?9"ƧGO̖GL%4inOGtzm?Tdlfy53_H/LDvHXدu(e:B:+Q$֏}^Jա eמ%3-2T́v!ycA/i\c?fHyA~p"4iL"ywTwHIh^{ko2tB>7SHpD>Op1M"J!!ĂCvJOf6@K C;8]swя9EC!\•5qL¹x)$fL1߇CMB<9? ͨs@8kO9M[[KliԧO=SpyS@HJN)hЌs)ĚWu"[ aI/mFWv1.^{aB/ 6ྫྷ N-jHMDBVw|2T䶎h|qϞvD[xA r !}N#R+H(i{=*3Q> ЇT&pQX7w endstream endobj 102 0 obj << /Length 3165 /Filter /FlateDecode >> stream xڝZYo6~ϯ}/$ "bH:G~VJgddznūX,~u氉6?eFh~Me"2rsPJep?tyӹチv##E&TZcmbkMq4pX_A*]Mᴤ5q ǯhdU*a>4>x߃`ÓmH,Iٯp77Uh ESKU ݃\%vc޸S^v,})3-2/v`v55Α8~p[8+dB]G/C=[uL;Eٮ ik8, .{l$LbKQ^A d?_%Y@@K TT;DE܃WAL2aw^:[SVTeTSЬ_cwVJ :$3JOc,g^0JmeIED ĕέy nQfn805J%cTG}]_H}TJdν +}!$(M[wûM_!Hw)y.& sqFe1(0Ԃ] 35mdz@[ A #$=U[@P/r# Ths0-}r~+.yԸ8@Mƒbɭ݌P-4k7DkÆ +WDJHl̞ י@ hm^sSV0rL}<^ZV88Ս#bw̹^c"m,׋?H5 JjP@$PK-Su;WhoED?szG8&(#_m}"k,'IE?)!` &M귵6`Jҹ탸i.5LI^A9{l! lq*m)c㷄 cL9S@_rλcաnW n~(؛}X4Oo{ra>US.=/*&K@p_Tg aZd"=E,PdM_$0L (b턉tD ƈdp-qW;#Xor>_7>@[CA5xk|'qlWJӮjM*ԀVXogzWV$3ؕlPWr7nBӏ>0 bF##LנW\oq>/;&"jmm0`ǡ=U1 d.sKڞ7š¼ծlb50&#l=jnk?b~ |Sft#;ѐ)yR?>G2=\JanGs"8uJ9u`H4}yIӘyW)y >HFwf1LDG)o+p%J} ^|lm{9X 7BT4/*/-|vБӄGe|VaW.DN=@?ziy`2&ҧcockSKV0E0B,Ac]5_DwevaZM_˅a%+;[0NSfy=XOk宛K?G_VT'CpCívūd+@F<<\ pY%B1Fd7C2rFc,A ht D@dOgĹ,}R/??T>58U k KCn_@H`qkPM m)5@aC4Uqr뿭O*- b-LW=sWL{2\CGơ,Ý,[} ^[ψa!P!_+I#F9H17k!W!M!/qJeD4ziunpaC+׻ڊT.+U {(t"\qnY), )3}g}KR!GZXSK+8zr!)hwx2Sƀot; 3Z_>M4,Di  ؀e&!q@MSQ#Q#GvewLya%|shչ?y`> HLe^K`$RC^HgvBv-͎0=y$|Cj'{v=k b~M8DOP vGO.s&t[&I[sPR9(=YDrƠP[X!lG+t3#NoF:";CW2^KX!ߔz --0msb@ 3Im^t9B*ihߜ>,\,>1$;5MJgNc^*CcAL5}1+Y ;RNF_q)Vd˷{Dt?ON+ !~ȍUh+yODULyźx}ȅشֽ%I4 6Y}?4N endstream endobj 115 0 obj << /Length 2421 /Filter /FlateDecode >> stream xڵYK6ϯ`퉪21[RYJ6DMUF$|$en4H4gUNnk(v^载 7,^9V<dlvSIoJ8+ '43sɦAdqCXXwkr&\V֯fs_@,& 2H!#g…L{Vda*nV$P)ۗ}7\|$A'ym2 &71{в^I*z#dwDk@ "#!ZB5Hrb*ZP7uV6<^RA!|nt"r(ntpʮs[tlu7EmK3m?!O&.=<`rxК2b%v+K ;q TzQ'ę/2mY|~?*56~ɻr(-sE)lUuι&lgu~FdnH#}=UfV֕6{ s>/Ո6q}g/vx̛vtGfn ң01yGS$i|Cz4t 02{zXs)  e}fYX:e| ёp|s\5z{Z׵D<Ö\+>sŗ+FжE`$=U\8PI\ ixǢ_|9hؤJnrK&%V\&r`\5>#9E.h8Znx;DS'ue%/zeE}h/8ۅ,GO2>cڢGCљ!zAiKULeQ(VqH1eS:'L-NO_©˦<y4sGXxm\{XiI,BcbOUihUsx:Plvَ3"[_d p > ^z rpC혻!Spw3ɋ¹-Ƿfj5gqKtu{nl B?)cɽ8W`O9T-f|jT4 endstream endobj 130 0 obj << /Length 2857 /Filter /FlateDecode >> stream xڕY[:~WSE<7`vxJZaɱCPC,KVu-6hI/n\BDabq^rFyix+j a3UW_oc9{ͩS*(Y߰ Pui`%/qjnvu cu }MzW}ـ2}$8`+:%`Ms'C#yEck)Er<+TBut +v()").e7[Yer0 my nj͚ vf/ w:v&պp  {PՑ=DsC53x_UxRxjWwH5#y v(bn@e'yXj4Damƒ1E^5bY٣*sZ0XP9|]nҞl; 3lT93VМBuo[XJwDk>O~)l01 7{ّrPUIE8KvPt C"q.qr==ytL -rf`Dddw?!F8rx|Y݌LǓbAT;yLN*@#4 #E[@9?8~;|D )qD I]H%+h~@ʼyۋa9IGeo-hud[˽Sx)bm)HZi8"- e>JxJ2xۦ>+<hYk3cُ崜͈*g!*.PzEOZP# y<6mg.`Pi Mj K!G:M~bhx"a/O5'|%q?i Jxh%VF{O#afl `SPۛ3o qmR-+Ibx =,*ٹLJFDs9qfo`ZKy䖆,!!9A,T"NdS#H ߩts =?dUJ`4lHJIʩE7%'@xؗy!Og^vvmi9c uxfgQC˭N+%C!T)A-t: t2DD*2/&?~TI,-O\2Laށ=MuxWA79b~`llGQ|* ~@0JGq}v}}2a*|lvXٺ$2 ;hZT,aq EBSfu"YDlr-p9*ΏsA(~+m?#\Z/wpNN4@Q#̲9Mxg:PR[{q!8@rL L0:38%*tɘ_S"B0f/pg^+~b_Maa5n=Q9RP4—\آ3szɃkOoODx8&.*P#J&a^ʺqUYPo쪮z%T e_OP;\P`285[tT+oH+L ~ۏ㛯^~ݷɷ~< @7P6ä7.7oO^z*!!򏷆/,͓?:B^+rjX~FCg{-TnZ|~;Ȟ 0jȐԼpv*h@dG  grY is k#ʻI:|vD 90G7( wH }=ෟ} Rӽs}Ⲉ*Yw=ߜ ޿zR Wh 6:C endstream endobj 148 0 obj << /Length 2012 /Filter /FlateDecode >> stream xڭYKs8 WL(Jz;ttcjK/@PʲKLI@ gY8[Go_ 4L~= Lij>qN&Q(`},QqwEnt.̿'ޖtאE eFնh+?}<+,sy9Z^"UzU33eZ|I|&lȉ{ ]-7 3pp`Q^%Ϋ=O\ λUC3(/+4]MEKdG;YM,'Rorw, neY74Ac=8[x2Hhk;]t0HZS׉Xi>9jGS܏"4NdMXs聞*ky6ev &c3Ѡkx|igɶ/9jt"oKT+ܘB[WȔ<yd7xg_|~?~. 䑈(^%t΅lƳpdb-Ezo[,.Dgo]36aSeE䯡 o޽X~6Vݤ$SaT5qUqz#sx J\oGj֡q0Y\ P-L i s`V[68@% Q^igkug̽}?Mۉ&qZ Ys":y!!wՒQKpAʞ K%ݮz̋PM,y X%Fy<*/)Kxohf@ryr057jo ׇKPYW\6v9S4lV-ڦB)g7v6=: sHwQˡ(bJÏx0:A?8C9 נ"uAGZA&=D p@645P|Ε-˂V1yI7 *stdcLt06U 'HnyèD];H`4^)TMPx9 *Wg<,Abi7%4ɕM$a"eS ȍ0t#ܮS쟠Ҁ w $>瀛ugwetڢZP4/j tni& M&nٱ{ 77ZgP竗f" ϭY_Vj[srT UiE{+b0lܡK8vj"'G0巤G>qf#~lq]vVm"Zfķlbs&" $ʡ1,VQ# sdP̄9anӃг]gL#q|P-RGH )S@ }E.o-mU/6d[ɶ2mv̖7c(' qp~#N?'uq"2= MDp  ᩴӸ랑jq 'DAq`Lqʮ8@ޓ8Lq!T ل gJg,-T)[ D#@ʠR}=+$)>`18|@Hh{sK1tἜ$$JRJvA-.C{mjEs5*\tn{p_ʪ ڶ|l 7פº;v(6t:aU#.B Ѥ(_dKaJ6ф>uf?2Wjkj}پz^[&+E[YA'֍BnRu?(;Ft CZto guQ>|(yY}ƶ󃯟swJSo$dmw8=E .3"f.]ݿ^' endstream endobj 2 0 obj << /Type /ObjStm /N 100 /First 801 /Length 2591 /Filter /FlateDecode >> stream xZrF}Ẉ* {.Uk\%.ɻ.?@$aMZ %_R()FN*pse@(,"+2*EEVapJ8ŤغbGq5RYoቲ`|1ؤHf1-~J;R^1?dzS. tCvϖ s/jۺ9Um9i-F~Q6msٖܶgz7N/qKCvnhZM9NJ_4W:&U3.Wڹ,Qx](JjPI$Au9N0Ed: Θ/Vatb{M3{>qɜnH~~~ޠ>n7(Gmwk|XCb>oV}b"c-pL؞Zۄ  zd/P IbC }tSqL0:uIQ0Vgp5B8:e: =l qAR¿9(Qx qBGgm_Gf TEa-ka5qHb!hqKZNcp7^2;\],!i\ґ*p.~Z﫯" .tH$f$;r̯z=e^'W]@"}&Zdfsbow8,qV<9kۋgE}LxrZa\".nOOEbg)t.Ȇuxu[Wo~CECEuHրبgE<;/GNlD NN:vE=LH `\JHlcJyȮQhY혿8` !Vz-*ʮӝt~R>b^7c *Gzpm$Ib"v{UL5rx>|:+'ǭyq,`2˭G_ NrK9lr_Zp-pNKMdS"b/@\%dћc:8Ƙ- +0HYU9mq q +z0/ Zκ.2'}S+e NJt_#0g\dku껢c-zܐ"Ii#=܃=^CNJ/@Y/ 0˛x;呮lOz;:ӪUq9>i@Bmq)RVQĄ]] C؜E{Qs{fFk8~w`K`/I[7ãZ7sgtT' kЦF>Ɗ;@T=H,j@QY6<‚ HAɎBnѳ_r|@R$xFxIQ]`@G(贉ql\օ2weM{5dC0V|4&ѭ^Sl,٘f PSʒS*|7j-\JoakcQ政v~t0dJ1%jmrh yE6k'F'7;5̒m s綾C;[r(a|\-ƨ(יc)gzXsE8m"r&l1ˁh?Ξn6β`IRNBͣ'kyF:ݹnB|#1-CLn N B"؝;t@@r3>~"- wL$| Ex+K\R+ԧIJ7 2Y#fB:e!_FzX<ؽ5aW f=ֺ#ӈЌCfȥ(ގꙜR|z@br5bF >/p|^ Q\N/.F9.ɷ\nxٹDu|yΤ]@i'6tV#tSj$t\)Ғڒjna}R O93_`V̾\٧+4vf}Bnk8͜Ik 9+hIw6Y띄(f-&`PPBhIMT7x4>vo2h`~ϪEMiiǫW/a-~V>yq$kR~ endstream endobj 164 0 obj << /Length 2837 /Filter /FlateDecode >> stream xڝr=_\Kq|o .CT&pMD$a7m ŜOw`=0~/fW}Vێ?-__fzm82()/q<}veŷ˫dܳBK,6\|,xnUZ̲ 6%)Mp67n_(2(F@uQqF $l;ϸ$W+t>qjp]˨'$QB#Nux93o`kSiYQW{\υ]*NOq/%Z;00G[a:[Y2YzTP*v̥Xʨ,7sC/4ksjiYPq.qI9i~7,CrEHp_eL"ˇ6BJ)qeurþiife}.hBq?eK.* URS8)0zڰsl]ulEd(&I84y,O $딽ZnjѯOqYNҨBm [Xs鑉hWm;m3,|a2k&wFA$C:k=T @ 1_%)8L7\ _4GQ'/4r1ߝf'^ޜNƗa\_;O[b23ke|ue+,d4kg=IT*^Dxw`ſCO'M Sn#(}Zxr~b]Ox῰ˣ%fѯ,ͶO֖Zl]_g ZE- m3Q`#Y5{I$!_h% Ms=^ 7ݖXR,җ)N,}Gsm)b# dC&Fj6a:5A#? ䷫S PԠx>V:MI}TŦݸkݡ}H%,eX;.-4hYoZIK M^JI齕Vѥ{ց^KͮWJpnKé,}aj7*)gkX=,,Zisw)iSEy-PnHqRA]TT?īZ&]eS-hS.gi3OhsѤܨtzj( RI@k4zYnI萖2G@å򗤸Sir^QM}k]fsx^_Z{׫MQ+tt%#Ң (1*>t:ODl-UVMcIuc \><>|YUbUyĩjj ޮZv4 #ӱM(Oh[u)hC#4+tF#O>=#۶5z= `\D_ɈebNDh%h/\]NZmdzQ͒Mf+/gf<(ydj:0Wq![Vȅ[J J1L>SA[s:n\V6L'QEk Gב1a+t9>@fn:> luBfHܙB[JDkE)d Jy\rU@l@fl Y(bJSl.q[.qev8KL3jE`M`K` HR6,]:6k A{ڮaXnr;AFnVuH8*y<[>Kf(2!Dts3- r4l@JcOrHi]@QH"a;d+vj yLkh}|:y&w`gDeX@ȐסGJ5t#CD#d X![6'=TVFs|%0p%KH%߷!fW؅X"sw*DZ9k}0A;y1hdImzVCvR`w, (6 Z}/×>.pDE9DJp/exބaڻWdy?:?ȁD%O±lVh CV<*Gt@$sV'T邎TuV  ˘nBh݇8IUQ@21|U9|RhAb# <G ܖF%EBܠ]=Xn=B!A>xF[ (S|-Aߙċjy‘zʿ8UmqHEMZR<>@߅ bAiW*}^|6u۳;&4 0ܠHaϯ(o'H m(L} !&i0mE bA;lS캲!g~J 9"lS3-z5>0{$/T״3xCY!gqi'3 E%0<[f:9}#TF|GmP\+,eJ=dY`'zЈtu\XZRVM;o삜nd-',̫WGK%xX gbA;UJعr 70醁exa۵]s5{!0 endstream endobj 177 0 obj << /Length 2206 /Filter /FlateDecode >> stream xYKDW(WB=mR$ 8hZU$ͯ{g\@Qϯg*X}*7W_ * <__q;x,[myJm6]Yɾ24)S7\]6c=|)/"_j<;vzd㝋b$ ,A}F֦6*B S?"l t2Lz?ث?+9v#dɂ78KV֬Xv >i}{8PVȜYxߕ'{*\idbalj쟊Y뮟ڈ8S dVXXsm{tѪH-D]@M b@3rMQ$ nwZ⇟Hx1 $K `xY qQmKGKE$q0e0P(VsіK 2c_y3s~O5zud@uH&iT;5u.{s7􈉌]QϞ؂Ɓ2 "u`:P;te 0iCa;\ݠ#^ 3bkzsV/ r>NXjw/4ӹʇasF>\i7kPҔ0ustI T*vvEI颶'*Z{|W6!WzvF9h -F| 0'E<$ W)1SZ{ZV%SD86LH2%1P1쑗`0tAUvVZ44e KFBu$xC. g53BVf #|AZe>VzT_.܋]r2fTn˥*0C<ƄM3}?&g6V14-y VW; 8\̭닥kPQA'mq57f_jKXz`C&':/sniá{c}[;XUK,Dɰ5nq͏w3z~1 p-Y,̆,}W͝]xL|Ş{ 'eQb'j/%"_7.Z%J U:d3T<&e HZƞ`g1C&W]▖vThxHkmSK ^ O $ꙫObIc4xN-Sk!ʼns=,34My_VeJb&,"E˴֝-4Jo|$e,}[3^u>gap,}\wA@!gIQ2{6i!ynSd+KL@#cG'uz}k l'7$÷v 2FS'xBA|M-?=_ N9EWW{d2hd7֧vziBܻa_Ao@LI> stream xڭXKo6QbUoJzHn6nw]@mѱ =3,9v{>қ=μC廯Gr3#7Kg\>,bQiM-ޏ{E̶FEĎ,Ku^U& utCy:7@SGbg'_;O[UӨV@|;<0䦥SA"4Mg ?t(#=*#,CO/PaDlajjEqy`fT}IFqdQ6A:UV碚+"~W*X*g&$ ![}%Q;ߩKy{5}.bW.KveZګv\o_Cflˌ1׷5Q(k FlE9Ϳ6Zh9m*v;K#pӗ`]ۘ q6"37H\j hV-H&2 t(ԩ/)`Ǯ&2ŮTDE:Y RNv>WqQl{sd/jQ}[7) :51%u&B 0c++S4&ukY'p$Q.נ]nR߁q$F Fɸl7Y- 3[c %,Imނ&GQr!g^imIq(i}0vBn±U*.yip.HĢϿ=$)pգKCdH.9}zpOLO˧ߜbIr+ouUJR1BG5!ܛDOGDAl@'S }Mq278ԙG`oPGO11Nmϗ$-;Q<:^b@}G Xp]ٞb_l8)>EE)Wu5lA'{m Yc .i$?^1t;gmJ6 c {E/ endstream endobj 195 0 obj << /Length 1617 /Filter /FlateDecode >> stream xڵXK6 WyRDz<63vSW-豏lKvK|Y gKXo뒀 չO,ZfB _//A#."Es#ؘ_yVk`wAZ->dI8ȑⱮe:`BG?4~!2y2+IڤY3S?ʜ&$ WdNߤD06K?Db|ONv )4a\+ʹf뇶Q]01tf-j95՘vsHQL HJO*:$0  rg'?Qb{jKg' $Js@9f D@LɜpEפ8>g |y݁}x4b1s#&; ;J0u~4 Z#l+r'9wwUV}!onXͭkRg^Cr) 㽀7wZ MS/Q0Is܌p(a)hz3#:vS4UD΀J5#‚ٰN w,iZ*32l3;:~ҨtMXwڡFa\i:E #WRhEKov N ,a @]:f6hnNÓ4:4!w4vD t+7!y`إr2yڦ$k@D |^\оU6k5(OJ>+NWvV۔y]xU' i1fCą:Oc;S `*0y8V4yni/EWmu1-iZM -\O/sᑷ{ Y|Jgj`KT)ݩıuSW R]g GT|SSk4HMc2ßK:ӆj|W 9syʦTV񧏷"+wo> stream xڵZ[sH~ϯ%U*I .ʃYo!T&[ՠhG[_?M-!r9Y۰\ .[S& ;1׳&rS3^og?"$s(WIOr(ͤnK}G=jы0~gjp -nT0&[=gnWIu`(4zӅijJ|E.a&/J8E `/>А0S,NozRIUİ.&0>hMй4L^Gqݽn # 2:#2)6YIF)J°2n|w[T>w ?C n,]x o *z1Q)-IÈEiXf2<ǽ0d[-kYMwTZfe@SuaF0Q)YDA$8fV *#܈UTIha*CsZ'U*cV"=zn†qXw0_|o2r; SA{YL|^UMA)sΪ"f 1#:SWd#oge(Wѐeel?4| Em Fe8U3g*NW fsNнHgTLyu0@#0X+Gl!,g9)\gQ؍ZmԸ.;` cU"zJ uNֺև q 0s~7`5TIg*ݚ\AI ȩRgs2"M+OPkD85 |WgmdUuU(Z*.V5Ԯ7mQm,|u5T2G {r ܀3W1-niMy0w%VhJ*E [:&MEi>ۧoƠ;ļfqFElLHވ2`BWXvzQRM9C/z/tЪ,:,vDB߫S<6@? j^<]qV`>fYU_Z?% *S|1b5ƐQ:yT, _p MFhqԯk1 `phhլRGM<[^ͷ4Yw#S!ٸe3XЮC3Wި6I,0.yD| 2xc"j:"XK\(^\e ;H{e:}}io5IXaVn^C+1u^PM篊O|)֜z⼖o2ZuA$̗cG~h! $ȭgs=:T0or肒ʂ!~e=9JiVhQQmfH/+(οٻ3,&O. yLc5;ruPCk1^SL> stream xڵZIWH(vq3K؉cYIk 8_d2$GDR~"[{g?r=_Tb?pa/ Xn̓M53.7+aYWE*\σn$μ?70Օ+ع7#] ~e*j?=#ڦ5yORW㥩ȞAWWVxH~-5j%w2692mkj^/n%gdFZ!=Fiq/Y뵩Z29=߰S<7-(umZ ̭ADV)3? ^ogpd`(JW- ,"@eQnZjDX!ya#q=*H"HضBDR]ç4My)pLfcN孶,V5ZuC-}MLŸv՚[x;Q]#Y8E׭ꖘbN,m(ٳhC(oJ@C 1h] ( WotQ/A5`›ij(ERAIniBZfYVײ̉qՖlN*aj̼a&1>#3##gx8$\0FxVڹ i , ~49 L ndaqOۅ~+_#p3k֫ Neѳ*[*PgEɘPy %<3+ruǩX3;o8~OM$z%| A D Aqmu>HB}'!8BJ pwm u NT2A7\af r#͐t3ïI~DFƓը/=hV xiPxV|qCwOs~ :CR"K CFX嗔$y׮ؑ!Dw hZ>еXy@H )a%x9Or5!chz>E@9e%X Mֱ01wb*6/6>̹Ve܃xc7ppVkMSQoL ݈DVꮩ `ĥ(y :խԚlˌIlL0fA߼֕8f|ukaYkȣI'm,?㲶hZ˵.H?M[j7vJ29:Ew+%X/Afxr:[骲R~րw瀔C(e%[@,` VPW( Ovzmjmt%D>}da0+:dC85%ϡ  w:8&yA]+4Shߙ1p%L6#Wx̂\1 :J> `˾ad=>w̟XY9+|"ϽKq"zyú}/V'4CgX781Tx0  t#Ii݀>+ulf jfx' 0T¤ qMK]9B0;=Ƃpu? Uzj3(rXT0r+8 q^w~\nռX%e0 k{|6O(,=ke@܂Z|\jVy- s azȋw? *47MfQbC(7Fg|Z-07hd5/ЩPt5!@p9btR!no,eLB ?3⊎<]J t:>2KaoИs)IDR ËNnLCFw!N,(Eo8nb3[4*M}@Z&kVȚsL;2l@Bc D 5BoE|9^nγڊ~Ö,C|Aj,ؘ'V欁~ V@{cfq3ѵ^ZY?E'VSHWG8͋,[#C=odivFoЫJ6>˳tEs۹-iz)23ntB+ǥ 7MDKL#pCTڐ-@Y"ռFs6[|ABmtkr:!q{ЖY##/t,7W#Iq{] 0Ukt1AXZD1nB@dWCBtb>=7v%dߘnB5s ZFt,!XcMD7}hӂјm{9V8Rt֏xq-ycm@Dn? 1&V/u?|uϔo} WU# /x%~ -6} uò< [y9W$l0kPDdI3hf ˱*r  꾟|`-ػ5PċҰ^&i^,GiN7'!2 +(=Yޗ JWr{恗ҷm%`uR83\kt%K?g֕ "\WBJC=w w۷*pWAQ+/eR[yyˏ ݃asù՞/lϱIābqO` G~k](3NYxT\Y8}5k%t`-ԏL856P c@C(9:Ϸ;Gq@MEtyo@U!iwUWfc{Oc endstream endobj 211 0 obj << /Length 515 /Filter /FlateDecode >> stream xmRn0+x$Gn Hh!6J%9H/>qw9;Y {Y/ήAV9QAG(5ZGX-h)Yd"֤X#K_-~Wx;g]DN_&Ov>QT׵IHea >$l&XߍNɆ-[BLÂgײ~(]ckOG NY]ʤ2Z)xQ>0Ŕ\[.[3@bqi+h%Ļ99^vAtg m_Zv':ßݐrlQwFMPzI&:5n0hZ_3 2 7v{;kTq^<>5*pO듚μk>x#&Z'K/mg'n=S38\R x|r~[VA FEU) UA5]Ջ endstream endobj 305 0 obj << /Length 1032 /Filter /FlateDecode >> stream xڽ[S:)h4>oqC =PZN;C 썣9!mhlQ}fZW.ʐ^Gϓ3W&9Jۋ-P?ok{,#`]}߷(.0>;Y5ͺ&e݈$'5MqE*^6a]V B$?DMMH}`,\۪4ELm.HRFq9ny},M8$lL&%BKvJ碝pY͓'H)z }T``CD䟮J_{#HS;Z1#KXF.i:n*!=Z`J +TL,AE{q`9MmNJ Ô^P>l,M S5bFrlx:SJ-/!mdYVf: ɗ 0n@8)RimJ7 RqkA3Lʤ.)pEqՄqTP\0a?4(='}[BNQՏ ѺTqx*ΰgdYYeډ Lf`/LEI94bi=aeھ59cClA]r!|}Az@͍֔AޓaocF6wL[}/tt|EG'䄋v;t"t'gEOMQG#uOK:/σЬ(; t݌py%껋RǷui~C ;Gwԫa'C۩Â,)#o AAE2p&]O0;߶N%P#neLV7mis#_ʠh%]H4>,o<?B;+e  endstream endobj 155 0 obj << /Type /ObjStm /N 100 /First 880 /Length 2466 /Filter /FlateDecode >> stream xZr }c  *_b'QAkO.[9=EG-KUg>dRȚC.W p%hnj0\-k eK5>硋%" ǯR©.k ROCkxR%] u oxN/|o QA_ Cn8V}PtgPe{ &6x⧎Jk=MBtk:=hI{MO @ZLȩ7z+ Ib9ma^i D; X 0X|c X$ o7U0I7P1{@) M3bZ$`TǍlgH>0.wc1l/;V5T8oz8TD2\9jmU;)Z "ZM1xV%/нa[^3@ lE' .%B%q`p{-rSR]ܻXUX?>^->>O rߗ!bY)kUtIsg4 {yX>YXWGgO?w?o]X caxƂuDv,/O׿)xAM˱3RЬ޼y'w"6i%"ȪD2 cĚ6O=/Aps,N֯0SGW-.BU*\J Ge,߂*8-կOq?n튡ЃUu36w;1C*}ъmKa&1%',bMr >~z̓g4[^01IB>|H_l?`Bc~ dbw m="!SW3~@aX!r`>@bR+0}qȅޔ ֮9. /r[^l6xoaޥ,*JƖ+sVlߓYVN`9C -S$Mu[(TQE\a1)-~z:_~.#(pARlN6l2 e,}cD|odTP:72ԡo2|MJԡotR:FYF,Q(he42 HiCߨ }mBSF)}}}C_+{87Z.ӢwMԆvش65+MW"C!b6@9{ڶc#ӹR͹xlhX@uL; kFo.bj8E# ̮n\)+EWKbwܸ,qXkh괄Ph~t?Q*SJZ0JnseOU"&$Xdn RY1ZWlZdani2m@w@4D LCS`FWj*nQylrЊ5)wI%ЏjyÎ̀Syg2+"q?f/Nfpb鏖XL'_iq2*9u{Y "ky 5A/dNKv}ShrS!!_E)#tŢ^D,<=)_rKXP+ykLkLզm}%2iϦ#yZrɶ/9V~Ӊ,6e,zLoWU endstream endobj 343 0 obj << /Length 841 /Filter /FlateDecode >> stream xڭMo@{`٣"'mmGd尶7.д$b睝e#f#R?FPQ%`H vh=p¼fbNM,L'&KDQ3lp=^E9vlY2/V-,=. G[vp̥͝! gZ׭œԠl L(6kzx|ܸ> ìcouk܏򘎧SKqJHli׼/ϘbUP-Mf@e벀l9!*Ȭvg5uǝY'zvs5Űz]e6r=) \6Nktatm8nU`3 faU.v,,H#T#$ ΫE_]G kB(IcXRяvqjur\h!& jEIwL$%M[@O 'Ƌ/YyЀu2Tuh&'.ZUCO6áXzu^4("~f*TQWęn#cq==rp\NZMICH>7$bwDf%gY:뒘 )R -K^HY .Admn"KSgbDr,FUH0t:U&;VNA]8iДdmY}z:,"'-!^MWmzc),>au}߇[F[ E> stream xڽ[Mϯ1pX_, H!"/#Ʈ!=N[0!RvUŪWjVZQ"fjqbs%t\LHYDF4FR {$>x'́9s8>ϜU[)Lm2_Ǔ:^L:Q̚՘łQO}64N ŕH+nXɻ`TɧF&|ja RFVS4rq(&^z9K{F uuS#K'K 41Jg 4Td8Yk1z 6(!2Df23P#~346˘)-O&BxZQbe5~v̤ 9&oNX*45.&N1Z k64̅O9|g;> !|ssEJX Z9FDo6(0o0J?aRQùrG G\~!: Y޻Z  {ھZW^oXFX;n7`~K9L`z+OPB@dzE74>zJ]6):<&6I]tA!+5C>cK.G  n*.p /ڈl H^Y17, Ðwp[{ `C`$l zr(!"?vd$]dB3a.[D־aϔ,"7c.{ 2yA 2ҁA-1 v+jg \ D?zWiD^/Ts `H3Id7 CF%o%nx;X m0]&+V8i<ְFrř9]4 ȏGcf Ұlrx54{01dD>1D6iZ2&2BrKnfCVZjs 'GQ vGGvm] %|"]c7ȧA0$"tI zkŰA x(n" m,q9kb4Ǹߕ~ f~gQ zs߳53a[gpb3n;?ЗϏo0Oo1{ꗏs>{{ǯ7?%(v%׹7lazݷdmXصq:/WiOĎ8q=Ɠc<=c<=c<=c<=c<=c<;Ƴc<;Ƴc<;Ƴc<;Ƴc<;Ƴc$ `܃]o L/Y&Ui[&ZQpH; ~iaJ6G#'uh G ԍh dy-t`A; ᦽQ ,]yUbE8T֏!֭o *PA g,# MB/c;SYXG lr 8W=tM!t{Zh 9&b ہ)d6β}Nc Uo00<3{gF X$d?"yma3n1%g6C)آ^;li՜R: ͦ0]}rN鞱 \{>q`@bGD^{dxcSԺ:xa+f'̑*]3]졆=*<_V2YsP 5i ߼Y&tJ+us=Bg)0!6yo coˠ|/-(ƞVnGȷzѡbU:*af!avY%zX/Ѕ?ms endstream endobj 358 0 obj << /Length1 1908 /Length2 14462 /Length3 0 /Length 15635 /Filter /FlateDecode >> stream xڍP۲, aBpw .K^k-F{ 1Pda03123"PPZl9GP:9[KC hz?3+d\l,lNff+33xbFyFB?j 77'a[@d}hbdP7<5hdhd.@CpY@'W) FKcDZX:%P796&@;w;S=:@EZh_ 􀿛`adpdi9,!rLP4q7r512~W3u#¿s6qt93:[Q#n,ng*jok 9#L_2L],]b!sf>133srpT= Y8~`^ 9}-_B`aZ@sK;wt0 ~O?^18%W("bb`` `a`pE#˿=t]'gu?1b<?HO9_ ?r#[K5'`U5IwAmtt*ZL,X4K;O ޷p~?Eߐv&l 99y _;}x))01كMQO&?"N$!!n3Ib0)C>U!vݧ?`2#=o-L]? I𞄙,ۻ8]_ſ𽇖Tmd/|_Hʃ=z_.&_ཧ. __?&Mx<4AX^7bU}0,žF* ײS# lMufoᤑ^]q[ؐ'g8vT_![d(r]P1%VǾ.+Ts">0DEQg€h1QnbL!EyioF?{W:wiBbMSz%,z|_ ^?/G꫈?_ ;$FdR@IE\LQ"iPE6RbᅌP%^ܔB;>%p–7?Ng/ SѴtي3*y>#Q ^:oy Blu7xݹp'._b-Ftkn :Cw@l M?K∠gq ˤn2c:TИRz4<#8= 2y^&tq~TBԛ gLs3(ucӬR(c4l8vhs>?a$KI\&gmh>ʵj&RϹQez(9xM؜-(u$w'._K5ͨ{Z>[''Cg0 g5۰zr]'])9FK /! @[$XoĥŐ쪥I=4CĚ)Z Yf}]>Ct<5nu$u6~@vٝ/l׎,I4?gbS:x9D"*l Y+U<&օXӛ1ӢYrZ梺lm7GDж4]#9P Q!z,!ھ͖`K/nj y}qwu$k$SlcDXdiO$(k2׏Bsl;WZ T) U_spYjk=ƒr]^q>܇tDc_~[PYQKQRzol5T]b^Wº> z - " k0LA_ɵ³-}6`EC(ui7 w9FmA]h5%wӘ y-zj{N rJ43{`^cV.~`N1RC:y)nN4٭ ns=S)tj -*8o;RN jrv(/eYNͷHhU$?T8CU-TnOGDSW!i:I_=Φ{BǙj)Kz6Rm*~l"`гRGH矼wݷuU|ů;8ZC'Ҕm?:&_ZYTaA3tY뻷rػgJ_?QoE0Q}Ue]H1&R0Q(˰ZGD"s^bVc#wZb;{m6Ym:yNNOKp/kDT]6UupbM5d B5|aEksYۣŜڠbzvVZg\ ]h `&(F叕U94UK2ģu5Xzf0=ss"tOyUg`k428,^;ff}\n[ !Aɉ >ʵ;'9Z},SPVA=bo8۞Lr-%BD9jDtyK n pG<1^(wGy0;=ipu'Q2_\VF}ht;^w}&ad?Iu\Etk0uF.{p+kO+!\傈J.::Nòۨp ?[Pr|߱LmH Eڸ;Boh Sd9Qq҇lt˗?xB'У8NtJ.[zU~I% GuNcAILQNT1DTw>OCYp1`7ƽ@ ^->%)/&n+A: Bt)˕. {/iTE0)*O%2 6BV˛ΐzii^.*I/:-7>1I{ʾBkVڌ-Mix(83_4=A8HcEۊ6Y%J?|s3%p"mZ0qrJB~g_}h5_dY]8qu 5XfR$ss*L\{C)ry}=-L=ӈ1 /-pϪ6cWZv=: FFjI=B㸽xr frq.6:C7{@һ7g?f6`ь%_^!F9/+F02cswBϗ;˘uD<%u܊&!t,T&R>>Q2]v968u6 qӳ*EC7WFفJoNyl-iΥWGV]~[)mk`]gBFl4 G2Ps, ⼸D#ʂp/^%m?S!]JT~~xmFCu>Y#.7e[ zA<?mpןZSJK;vBQ?3 8Z?2&.Ie']!'r_[ =YuT#/YY2qK8C48ǂ#܁}xu`4M,  D09} T0ɠ.\ppj<z:SK%Ƀ֜)dDgc5h*[7Z鮩9H!m{ }~DdgN+r`T Ez6GRi}}ޫ wqaju#UyrkuΉ'g̓pwC;듊2l*%bR./^ Vb4͝Aec\BJ*.藉ٶl7a$Q.SN6߃4L=W?ɠOY2N#[˶EѴ`C/>%5ڴo;_ԼzH{5RT^S㪴6̋!KhkNf$A<n[v(+Ic!GYtA^(8ǯ')P`u\h41E&`%*<Xk*p>˼l|ɫ'|')5}{73OB?N>XUC$_ ZYW273ke:a՜?33w[4r\͌6-k+xKscjY3.G;X4ݹ\V]4SWE~?Z|k6/J7f<ڞ 1IQR/Dr3@]H^lPLF4jZ*ShfhP`1m$l}0ST52AaO'&yR8 zrC\o}o>0Y4hEZ戢_kpUӹ< OztwӻMZ!Vs'ȚdJ999;H{#Z1x#w i\хLF#F4tJ$x$8Nl[DQRיR}:d۞u¤?xN /@D G:o@\,lΰrnK/G~[f|v|Ǿ.*"P*w>lPZG:ש8 )xCpϊlpij:FFerH~BsO줼_GaGv-0aN"^?5b'ꉉպ0C *={)ʊ9[%Mb Z "<LT3!LLyUھ66{0XsvL'4Q?:$xYz`.Sʅ7И&\E/,zjX\~k (XKT c kqק5VoT狆+rWO:Cj.Zk+!526uM %({ \s$, #۫Hz%C"~Y2+;tsA"O|ۗF 4/ńc(Umぺ UUE +-ڬi4xO!uqX8$[V3{l3ǙYXNgi;>YmB{D0aVc$eW^GL_!zmϵ2G"s-"Ɛ'Xj&ܩfbc'SjЅD'aXpղGHE.*|k(SCGh>kjYY2#`4,U5OB?8 Hʥ:u>&<ΘI4qD͍fӿLN:Sф8gP~:n "޷t~EqCڔ^|b눶f.?(+~.㸈xϛPH)V \} 6@WG5̜j]kLl{=\Uu&Pb*) L l wk'/A&Cvk 0g~t\A{]űIY4៟#g>Fe5p,UjW M%F뵀QBẌ́4##E8۬J#z;|,N f ń,I҄#8Km@ [l ̷[L/3NOagLTB.y f6ґhd Yv,Z['>qdF9[!Ġ!7 5􅱶(qٹ6 t6eXH )!oMoWsX>;Iav 2[Bp9mM gEP=섳!Ms ԻZ7jRt)ʿKv)wv슂hoن>hUZ2ي,<Ӎ5T:[7$TNaBK*,@ Ƌ( ͙RiX̅U#̍ ,kMlLȒݷu;/\i~Ž:Zd'[55t!WbbV?BPPAJ/h)a*,YQ-EQ`8# zF|짲XnEx)c#|[r<^ 1ع?<+*AO+h:;ۨӈt@* ;㦫PO}k}jװp/i*>>_OLMkrlI;XƃG?.u͔NqM=XjEϭ~Qo("a \//u [B#Z?U/P06ktIuEk7cۗ>ܣt}.g732L@^(wH(1H5f;\K)ÓgVRLrw^[(xj |i2A":  &A^fgQRkɷ_BEQ})G8Emfa>(.?W n1u|fDc'װ˜R5 xh #9=jQH?ڶi|[8_X:ؖi. ll$jLfJ-9Dքsp2TG>)^hF&8˾h^aGeP `y b4[= ?~8Xϫ<䊷[ցNo5o(R~!^d UZj-wsՉ0,,: gTI*WyEy `|tissW&IӢebfiO܅ou(ÂҦЧᗒ8^0Z" ̤w{[с\.f7NOjoC:<ôÁn߬9ͼYbtl >+Ĕf٢2܍!JHyqNlOC|ӂnj@WMaیlX|FeϥKD HT Ig2,[[0L)ltD;IuER<s1 ?N*`L36Up7X(WOJټNOaKӥz/H4[HPu>.B#H$TWvצJt^nͪle@Ց/G$U;f-|a`:a—=x5~S·sSBZx9s9Ͷܬ s)R@N/,cVJO3.a7r6K^IMhV07n|_oe{VYK۱ŊCw?DɞWO)ǐފzҠ '10r3UXtwklMMO4L|:by*KEJrM6jq'3Hjo4g:"7\Vۺű b9%RdG݇w0];Nh'LDJ}(҇_t)Vp19I}eL@ގs[i8s ?AYn)F]ND %%k(=Q R^>O>ta@׮t$UOc) YG8S`2rrCؔ<ܤPb wdica%Hn7#i0ڧPI)F{9Fr..y t+b1S1!=Zr2_a-sH9$2 U n*E5q9uG!=K8+/ u/g/1F;{ttdn~Ӎe{GjЫ(ğ0&cvBH_f2W{⠒̖&r5-P}Q~o7:PǗto~y0#^5wVyRzkTllߋvhդoYF{D8dFց %B8CN$- |?[ѐ/yB8yD]vCs aoJ]ԙf6L.N3g̖7 {p;I*%Jclh9Y~=ԯM}:.EO7h*{FP":?4a*Μ8S_5]x}0V땙@lZ_84>Ga8PiU3Xv9ZmuJ;fW mʫm"m0%5NiW2cDs?)PzRW }l_ >76uuvH\ֳeN,v|s2gn^V>K CUA'.W5!mGtf4^ttvwp[ynZQcg(疻i 㠖MIp"]7a  ԡʄ֟¹hߛ7_n׳S<'5ǁ!iD^}lnBW@rPSTDT5cxJjJ.:ѩsZ ryC8ʜdj G/\Ә<ɠ0L<'E-ZZM6vkSsP*\ '}$'4m.Ib!tmZqu0N5]qV%fS':Qj|6/c p*oI9C{r[W}kxp|P+!L]Nךs^t!+?C9IJKMvB }0֨S AC;kQ΅Gɭ/^>q Zӏʇ1XN&hhA zm<$o¾-g9.S(0A5 $E&v.veaqLcVџrؼ~atMt=gP*1=\@^}V>J݅5rCv-8EL*cdo+پ+|sX*ʪ+8Q 7!T9ãִ^|br:4m}^k$KYq4MK^|n}^TL)}~x ώmkuS8}=X´xTUwYQZ?Ľ8ØnA0Y@ZR  #^uvPDAy ݟ;BaB4dA%a|c8e@_ V N #k?:"S=5 |f̏A|bm>Ū~ @>KK5ꚠܲvq_ !gbx>։+/%!i!SDͱonbHEr ,R+,An> stream xڍP[;+N  @P\[q+kn-Kqw(8rOϽ7d&ٿ;js ѕM ,`cdac@ڃ#G9!t]_dR@Ce#@ ```cp,-,#Z q}K=Owlt(]m@/-M_!l\]XY=<N'K _:|}oBagX-\ k#/b_rCce,!^ŬZJRB a0sp9l]o_yG+r_?%=t/=c@^&=Fll/_ߔ:[z ?zous}e.8.Օ[N #E TZ5.ɵX4{#H `fgcvYؽ<>\^fOey;-^(l/ aYGKS `eq^XAQ<V?D/Ue&No*?`Uj^bj&!M/i|:K>ogj/| /|)|V+o=j^L/% _j _ T_*sT^l_^u*%˿w'/]mA%_/9^—^—]K 翂|Z9;<| W @XքV{0oORo3;@L ZuO)Mw%@s\ޒz4Ac|en wˁxwRdf-wo6Cw*PguPŹ^86]ɣP2mX8Eci$v3B#a gGݴrK!)8nOQABXTLH+[jyA=@ɒWaq{Yٵy(.;xQSvGji߄\щ"t~K|K0q5zuޞ:"}6?"ېg "}~&.Q^Ux"XtDhN fm֖[Ȝe!h<6V@nP>C3OآCeA" KJD.-QSeHx{=J w%cǡr&_U(N?r&uE ]t*VL j*Rԋ8x9m#2C&1Uɣl塎*緱 u]|: mMXS.aj7%^V^E3kXHb{6Tܜg蕄lc&FsZզL } ໺P~2xk3 /,ޔRGl%ØYf0³/ϝc ]zP$\:Tz-$l{4}~YRl%nE.4zqt#Vss1z}&A; |HHxu|`C Tcڇ#lB6#G+% g@՞g?،Wа!t dX-t?Kx$,Kg(e'g~3'|t邿6Z kp[ox %A4L@&Bpg`XZ*#e\]6T_vԐv:zP:.A%x>zoi;"p ` 6wΪA|vQmM[_;"ůӷU~Ou Q gY -^Ϥ>J+s4vP#YZyǰړE6rZ~/̗rTT-$6R>o6!_IiH=?~l3iT8G۶xt'v("C+|j )<-  ܅mLzTnS w-grWJ[qN$3(\ǖv4bMxgh"'g!qNnRj1g_HV4/L?Pڗ|cr0GO9u|13 Pd0^o׷K'olYǦ2 H0鹪fUL߻S 4 l%Wxؘwq}C _;z$k"|5 q`ս`Fn ,o'9$iۚv f4"|ds#ν!=l)ZdG``䪥Zh*LYSdЅit3gVh՜t$/<2&<%&*GS8wi3SRjE$$r QԂ=W;B@~ 4Da)`׆GJo+5(U w.{/c 4|7b3&\o>%}0 x;PXgbмxשj܅0 ԉMwT5ަ_/2)?.+ ?Jɋ qãjy)(tP~VD=i= 9?.݌W0ΜdEdBܼ]6MzIJ˷ORkI_,K94Ncڦ?9[]<qs'Snλ6 S3߄ϱ9\H&kIq9ĕU;pkӿ颌I]ч~^x*TWRB_0pO&0^%*=ǽ+tTP"S\6(2}B-rJ|rdua%3Z(592d &^Bߐw52jπ/[`OWQ 4TƬ 6ܿΠKmStlCshZ<0ՈR-3,wQX%QK7ZЄ; 9q]}&"0q3Mp+K2ʪZR. -u}۵V:/9*t*T\V'b_wPK/D/R2_dm]@?.P}Ęz h*,Uk8i%1~^n&~b>m/CmUV~m_QÊb/R:7\x¼AB @" #ζզ WC*Kk'.&j54_`Ҧ*Nٶk( m:^_'yF{cځIwխF)Ttಐcdž+0Hk3|T:))@HcvgV+V!9)qYAuhjWT*! BE)l[\`+?M+=P2X0fCkG$1rbwmzPw>/"oR?'G6&v.Qn7;EY nc2 8ژ,ś |~ 9& 'vATdᔰhf{[XLe۲IEO lDxcg‹2I&_,.vҘ.dE`Etgi'4{kS6k3X@o}ٯ.mٖ HK!yh!Xf+cA<Y_M7wh7;7 ʏ^P2!+N86'3KHxC÷q}~H61ao93g WȰYZ VdM|v , ?3̉-E_Wg= ߸0bG5wܫYi Z>r~j;$|_U[,̒11rvY=7vGFȘY]dLEݒ9r}=MϑntgHJP3кn (Ԗ/[uEbd͉׫%j'21jiLUV(\e4r~ܩ G d`@,A %;)d,hhW.)c@I6zk(S3953voU+ QE8^T 6{>\}d]~͡u'g XJtl$MӛւAoM=Y^a%IUܯ5X}%|K-f6k+aZZ Gʓ&L<>6s'C;OG ި.a ?#glVj3|ފAiLPG xh*(-{ؚ|#: ֘ߘ_jlqA jx~t"~4Wܻ7L);wm`U&*s513K;\Ru\R>b#[F*|Lˁ,aEiZ-4%SI,|hK 4Eg~/s%L3QphDH ޾2Ë :f!\xwM[cihkҴ$e?v].Img)їcZ^MO]&#ڏ;+{l eGi:] lx78Ƴ3%wW?e|'%p9u^Qi0ݳ$(pS?T1u͙D 砀Y8dvjOqWHK:r @UEKUAUG5*UY HP}Dz46yT;v.~MpT,CcxV=*#^V",fD<;(}o,qb}3BCAحY8<`HQH`Pm<Tڃ 5ފ:rp7 h\;a=tFw(Y~ V`;|@ܱwC}u|ÑFss\KBƒAVY S1~yk!wVrK8 Wý{1 O [Sv-Í.:y 1GSs]D_d 1'p0 x 0,uiyZċeXOP$Ic6|7kQ  ; C29+QxIj6x# ͏yZhR:cCʒ^2kuo4P"є߇k!R"VᯕfsX͆ա;4mn9OP* - znyU(-R M  _Sj*lX2UR ؼY};z;.eOgf}fg[:FURԢ萴K c(T8WJ&Cu!n/#{oRHDT£}htwN>n#P ܢ81lff6V Ý9iGwf젔MXVw?ogūMI2)\6Ɵ}p}F`qB7!T*ބR/ t`rȨSŒX j5bCIܬZ l"CN|>aoWUcAQu8Ouk#8ۨ>=t3% t3$Do{͝gy.z<(hO3^DR0{Z6 IC)D6hJG107KXff㜈(FpZcHwA`; LY?q!,7|OP2Ea+X(l"_Ȼ_^zO03Vj"vg јdzT_BM0$SI̲*h-ʱ&PZg8[لlwW~V%QŹYPe[l˱*~<.iq+6^^TD.[PkH WN=}Rpjѯ]MJdS5V*?Ѭr'P\ S8h^ 8^КDcp!Wfe>GhõQ~P*W6}3nN4h1dm74nHISK$PXYeQ^vEPq:-^sɶgNRurb ^Λ^ON+\gf[7G~yu@p~hbW(rYtij9N8 IAbl Lə8b :`kI0#uN@Uw_ލ-ÄD.̦U>gz0|pa?ӉbMzk jiդ$d)CĻQ(@@3?M,BRrѩc*ѓȹq>i N,Șs&8lWj7g2^6OK8+:ڹ[p{#N iz#D/}N=#7D}I8T c `CnSz+er9R>:ph5ɋR\oR[s$nLWOO<,HXqx1o% )~ȊFsnnwKZэ5F =/%u''9fgc__xDFhg.HF'V Vq/؆9~#ާp _~ mc u(>Jݶ _9な3˫J5~U4D %Έ>``sɛLM +BO kz@(sk{ ##3Yj Z2BvOA(@<*Ȼv  J_㬫apSiJπ:H;.)F;}ap-:Y?|>M(0gX8WN 9gf (kI󻠳L8/nr7E/FaN4@<߸V5ńYlh/=j?;Vn.[%{?'?}@eƺOeziH=D}6)#qsk/w-CRF2F t8ד[[0t/BF/ʱI6gR/t"S^#(kPgigZT FrDT~'"g-J3Q&xsNUd9# ^K?:%.;Kc2qobt"w#\[*?׼tȱn7POzOWGU LxG.`$ (Zo*u 'KCgY̙yv"V[zMORQYN_>mNbB=!7Ǘ+eW eTE08#U^ P9)͞u6j={bos4D^Ql0ߋf.w6B~O⢒ (E U}(Yl>vER G{'a,}ުUrz94茵^yhc_CkY=Z&]dV`z:}OS~VȾ8*u@)ȴ*E3f(Y"muy'}{r~rQLZYɪǑw5;)0ZH棲Pd29$d]t ;vP5̨{/-{۾)=FX K 5=rJOlkcr%䧥]R¥T~T|iCZ?G Fsq}\>X燙x˖`u񱶵X^RZH㼕whi߰Zw CN/JťT.(%Q 6i,]0rZpGTk`9u^ZP3@PS@"kN{R0^I]]KյDd( ݂G^rCq$إX16@]M9&-TԆRj&C~L8!'Rf]rƺ 0ږB@c"W 5zu<j(R%e}B8 gwkF.;=)|v!`t'xSGDkV(ȨreEI:=WESjI3Tcwx^48Z"QϑhM_i7Vu4rMO!& fxKSiñ)߾z5oCy,9̉-ʭI,#2_2rQ]%a>=-R/9Q!?q:>Oq#KkgQU_(-ʦoSYxi݅Ok Ą.o2YH@k.iqt_< !tzN,RP1NɎA#?s HG}sWZu h٢\r]*fRW,NE^6V,qcc= Boe+qB:46vԔy5nF[#7.L3Λ7%ٱD<h&[5gYLQfR,Z̈^,6A0-fbpPwo]9azmRU|2?L`ׇh<*}EӫA 2WrٜvyTè%/eI+ 6#4D6]o! 뿞{\N/8˜/SAOY'V1_7By Oq.lPf3E ̌Z:-ާ>ոhM q)1R3Yg׭0-lit^p-!K"g7 ҅J5e%:x]NoeQ9%.|FS5<S؈vH;(NU|2PdAxK狟ELi!DqKC`єetld!aWv3Fë4ەHot ` ڪFQ 4i&>}yf Vᐞ$xe4#rr{TXOHB>}^㢴QP0pk-XqeGMl[tIa~7~֌хܣtqOLqj*6/JaS<$U~j hg<L_KCaEVB*l636+9z*|$ A=G"6bzGׂ_rAחmr}x{7MƁZY|HCT4s{c '+8p4Q't|B|?,FNZ e#^~.z[/F7M5-)0e5Rpa?PK/FQG[՘T`#M+K0l{êP$ ziz^O%v%%v|③ڄ96/1!yqc^`;]eZ %Kq}vbK`&¾=#B]}_^q$jV!~oGї)w0c% <Ҹ:1k)1 :;:ZLQ M=u )YոfPO  zP~#zMиCig^m)|oU⨖O*Y՟m#ޘFL7D)SPҤC:4oT(=Km;־򙒽fƜ): DŽCF Sq+JYV· ?.y@穕'f{`uvsϨש~hjYa/ ky 7&84{_ܥks|",{Բy豵vbpo{ip0F2?fģQH巜AGK}y?ņ endstream endobj 362 0 obj << /Length1 1444 /Length2 6463 /Length3 0 /Length 7446 /Filter /FlateDecode >> stream xڍwT6R*)2(3CwwwHC 3#ҥ]" RJJ)HJH4|cZ߷f-{_{{5p+8 aH,(hAHH$0c`8Lah |,D4B$XL@hI2h"0 僆;9cqrb| 04 At Xg;D( `aXvbQ@#D;rXg! C{?GBaF ;1FHG np( G8# m Xr#~%CP; #p7@OU[@?7  8!U70P4`n?g,fb~ Gà{Y+r#p4A=4`p.`XH\LH\`n ~($ w0O ?-20b0'8qno4`  0$_+*k(i)+_AEE7_H/(A1C?C\ #~qwϞHG!܀E pV V_ws w8 qb@7 [:0}j`!89( p U7A:oO0}$+S;$ȟ6 '"~OPDA!>d,'S/v$pD~.ZTTe@Y" N{@nH_tFZE!\ۿ^ 07 J~ s k;V`_&I?6-!.'djڸt*z.ֱn8?<2w|g,USt/>K,eS_8iKU4t;K%L?4ĶQJUtn-D*ʥ9!v#T*RWLs;^F~ۡ"wqwl\]bvɎqϾ',T9Ztt~zÇHV~٤#iw\BGWp-4,q$\~hhto:jU.{+1I[!WxslɜDXk9uOxz `ç~O::9g=bqKusE;`zhFMv;8·] 8$5VgLX<@k+=Q;??C yĝnogtPDe7L;D˾&ۚ)>7jn}B׺J-AF'yUO9h B7ŵ),rO0M~V*B" S^ V[KK]EfӢӾCWa€ژj }yO\ı +NEńr.ɇ^m_k.*CJow;Z5 vO탸_zY@-9A.ɻxb>Nd4 FMO8Q[6ٴ-Y/po6&_Uǃ럚bh~؟ap##|>8]ߡP" Y)xwlj gZA2[,y4Azݩ%KLÁ@ * 3rM||.Ufz4, и⽈$ʩ']#i)^77UQ^w*2Xꕅxً5N[F(9`od[w]~<`wmv;\wg̐X _  b;I7~{BHȏ~8:go>HK~_6gTW٠y`׆EYzwȯ^˒d`-;dѭ,/|ξ4ˏĮϚ? m#xE>]!Nhd8asZ:S/蒢gӶJuINkd<0Ꙓ;rq 4QPpᓟ3l uU/]yÍ-bfIAu .$\ۢ;;ݿp`Ntsݖl_1"fC>Eh3LF!%9"$s-ǗPS jQ.[ΝhlCpp|v5Y>޻aAgPlR]};h/FB!>Ob[ywD!mT7` D hښVӍaT+p(U11WGu>BoOvUU~;Yl\ X.E=9䫺 'us. )G|Iokݯ J5Gb4 h(!XY%rpxI;ҐEm {es-A]dU&]kk{w4'Uft"Nՙ>Y=0p¹d먿5+:cuɷ%d^31Ym,!ӸF7#P mQc)ԇ+恺:}kRFʯiޅFV> ܈[r4n0B-|kf5x-du&9ھ=L(l"p;w[Lmp@0Ș^;(;*JHޅȏL)3|OLlo*UNwʦܠ{svmό]-XsٚTcc/7gl R55-ӹzljZ':NSH>x|Z8 وHH.|oZawǍ7;&~ bx.?CwؘVb3='qꛨuuwH|_Q'n倣á5)">fZ{+iثE.2f5-k;.=/Pt5SCO4؍ZAߜo axdFXp[ϼ VuuF>lIP ڭ܍&'6KR8wW})СCWjF =Xbij0f3Zɍ$ݷy{ZWRv),M٭vHoݫ{|yaRs*dnQq}9i6AzpkM?ERշՍ 煗.+4&s~LM{v19.OȬ^aՕ )/^ a=+2ʥ :ޜKVGFq= R"I:^򔢞],QU={BQ9vp ve;Wʐ d;"5P{LӗWׇdzl;vjO$cln{] $DPlEdދRJk<:;d'C)1蝕{0]"^_$ ~i˧TqnNQ7Nnˏg0gr+Jʪl)ћ5Ϧ[̈Q{OBGY)[2PQR= KqGyWD%.h(]a)CeG9t2<ɹ!w34PHTd"/˖NYb?SvVD*_eq-< |jUSv矵=^bTdm>~?f6*YWIjMYBKq˖6f&eokYVaƽ?4&aڀi+Qaz*}]pRr-^ε}D#~`geOK *xӹ!uRdu?`ex{[g}-zOh FRa ~OHI;w=-o%#Fi|.W㠊Fk&9r85 fa+Km|gKm \U&+.u-{%fZIiIB}ȼUx[6t Ch yކ315-'$)Ne$~ߦgiM88pFSʥ6n :]TҗGI2k Uϓ+(QtxX/N[ea=ܿ^ +j<=;80n~@ѕS';KK=K/5UdCF['ZƉ][j<8%8Pyzq$aV%iuT"Oac/h#!am\ c[~u!e f񵕈材bIBg U)i˼??oZ$7;KXvz V9|A@̧$e}%]a-r*w"ƏRl-ޝO7?$W"d" K4SJݱ.:/b7¦~IDxM:>xt@λy2v}DD 4⮃O6ORZGޖE!(WR⌨ev=X_!i(IGJE{wo_v5vX (m0VmEk)XT7!zk@T$EqvE\.\.Ӛ2ыxfˇ>;M\{qSi-QTt!H_@3uGh@AsG lj@bpT$vܛ=j|H94UaMJlp\7>g7-_}UoMʵ3SW̜b]@k(:9*ڄ q0s4Z(%ڟ4*x*$;j0W$d#/um2Jb93!sY [<ݟMI_oLH7p ?I1BlB^Pb\bLL3 pswǗ:R^ldmovxG8HE@T??2kdt 8}19 mZE0 y{'zؼ!|I\K+-d[b;<'6kNUcژ3GqyQw$NI3SYQl\t$լgBSV_t% r",Y$1[^4;E5oƈ^_N'K${Kdxgh5 _|ޡF޼te.3Hy5f} a>l1)xA(綍V / p}{f`߸p}JЅ:U(5;CUKSN+P] >0CyQKmjKm2fzŮFnE7̶Flh=;0~1>kTekMa@{^^&݁6eRX*pn,;ir̯yN%#A;b\[Qm<",!{郇uF/)3M0Ydģ %J Ԃ`Aʝ1@CޛTYE kԵOʺao~qcΒ^Ȯ,o#oo%z ];жe$7򝨗FJ>2jG݂{ LTbTdqfk#=ms[ƭP m^*>n%]zw~{P`I.(1xXQh^ (YE UuitӸG4No3mgړh=b30uy[F F7tszw\bSrۙ7U28GgS>i~TvY_ ݀7۹MB#Ȋ{ 'fxA-N,>W_tQP*N8^͕llU!@Iծ$y6ҀKbGyݪ~tCey ރEL -)C4=εzLWԷ*dr1rӠ0.xÏSqV͎Q`B+#\)P]2Gϸ݈vwv.N)~yfgA"ݍxnjKT!ZJ5|Qi*k7@Jo3W @M!#2l{*.'5Kܭ6x=”--|n endstream endobj 364 0 obj << /Length1 2696 /Length2 23391 /Length3 0 /Length 24904 /Filter /FlateDecode >> stream xڌT . ]CwwHww7 0Hw7R҈tHtKҍ4Hg>ֽ`7o&W`t4J;:1 $ll,llԚ 7;?bDjm+A X&iSrtȻ9<ll661ttHy,J,yG+" >,L. 3 hy'+'+0=fP]<_WƂH д-pr4s;` t4*N@6`; 𗳙7`TYܼܘf \ff ;3s_bj3pjrrseq*Wp,%nI\{=7+կ",ݝX@@9L"2kt,lXvd%WY}]<7w"Dvv% `9 Ềlcx,5_V}y]U-yƿ+W'.eb0sp-/è񇯜#o6c7#xkKnfm(%߄RAv |J3pߦ:V h rZ973!9XF4 h r{[k2;P`ep_* rR׉qp\\̼C#n/;-^-1 rtA5Qn/߈*X%~#>o`X#vo`8rU7sQ\#0E7sQ񁹨F`.johF`.ZoF`.z"~0g/b[YW737w1x~9}[*-E`v / jr Z.@Wo-c'/;x1\m~/gw9o6r?bo z`? 8o:\ix;Hnś? n;27|:&vv\o58s#9.w<@L-:;-%G_11vpS7hrz1 npW UO~GXl\,kn8cym`o? 8.|.ϳ< ޖ{w h8h!f[qIȓyg74zfEN{T5Y!.bGzWDȞ|["ڒMէwp' H5Ew@BvS9~ĺ(_{;[ãX9Vap uy,>3 <72DFDX"_ YJMn*}|+̱i_TyyҢV"\+,5 ZyaDXɵe&.*85&1Xno$W;{l?/̠>482߼v;>1<833vF/}_ǷȿLoQUpߥC%Ґoe\{lݲ)ְD\93\?d8f)1b%~'oy]jX}%^ڰ[j]R$R#ܯ^2jCD۠깔iDEZ [EuC-1b!fiSձT9Øj1h16PjX`$' !h|yaxj)gdg/cXFQ=)S=5"j^Մ&&> !M4 e؟64-d؜六W76~)2B`*rɛzd2c%,k[Y%4Aw· - Pn[U7ko|ҹ Rju ö]..rLNkqw-bI*.r*=|yS o\rG2(8 ^@dk~S`YQ1BÄ'3k)/.ӉHJL@Cj9U**d nH!<< uyOčA:cR%G?Ϛ{Rz^ CF;C1@/J DHB =ɛM3\*ɻTyʆ-K*sE:^d|䊖_ٵY yLΖԣsGu|7u3D_5=㹹aX޷6y澟/n" 'Ȏ j _6sҽ F) Wtָ)c>&G綔aڤmG9mLj;Ŭ= fB}h**sj4{ӌCdVlNjU)|Rm1z qE]rnrl;j/Htm 0ҰI1ia| I=xt&:gŞL}X&NDV ȫ51s!Ua?g͆>'Ki&L߲@SRZ5|i|'iW~WA+/\ ^'ӦO^^0cuKAwfw*BOyBLjGb ]T˫AT}ͻC2%9=/$߷$'3e%%ZpʮyLh|ԯ`'e(ŬWb2w z+Z>\Ɖ-1 ZE%gW1Gz6: Q {[|qvtv=K_bW[gSgn>ݙq} Tw.j)+$9jWUW(0GC`qV]wKwƓEKa3@ijDOҬ`1u9swGAO[(<o ,J oϩRʮ졍Vu ϲ,.8TUii4F*x62/0ڏoya,Z6jC[ {@^RXl-4TG? g gvJ(P[Iմ>d_3" ҀeP^~kðA3P{hSǼm PRՓ)$凎%6.Ӄ JZ]#DJF mⵒ+45WS?C] Mj RƧ `OiP-9 Q YThVptvm9Ywǖ7F 3I繨MV+/C[2eU{VJp3d:ZO׍-zHA3ԶC䲟OcTd_GLb#&.˷xP"R-•-J؈s i?s.\aVČ.hQA"rt~AvٔIi]SF-Nc7#ߔR5i'lU@f7dn9#W{P\^wGpc Z5;'>BbG ޿LWmT > <+\龿,#y)oHu A2eE<֛:HY{Kkj {l[\oa7$yYѕ`Ч7-,WOAC,[cvޏdɸ$jdܹMd2nًX =oĎ}?Cer4yweܵ*淡"/4=U3]0*p-][9t-|*Vi%r ]>=~@"yu-dI ^ Hs@>d.O`¦fV^8f}`^c_! _SLjz$I%+"oh9ԗ[tnŒrnF?nDXZ zO{Y]vn!HS[W~ЕGd8s+ KDz48Dƥ90؉\ƼŌn;&sbV.x:U06R~hlŐ36Ylnθy-,Ǒ!vD gv\{ttPjKyԫ{W/\"F \3/2{uYiZǗ7EIISG􃅗 9Jx)|%ߪ r=) s)zx/3ɪ,k#bQI*0ˏM> e ^%=x}SebȌ]KRwx<450(?pMdBL^JYj(11T<n97A\Cڼ@' ,sEbfd+- ؘn+uD y nkK}hkE.pGzV/MCDf^3ͽӍL2 !`:Bua ϋTm簹eK%Ƙ@92u|"hIZp/ѧVa粒=FAvwP܎ љiJi@eцj=r3?ȺE4EO_4t{Vj*m6t`;Z%mY5_kL3;x?1!E+HLI@ACU6\qv|5DlZp;~~6Ǔ0 MjEӚCC2$K[~6yx5"<[ ѐwSkg|}[: jl0Jut^*!-Q(ޓ+ -eQk N RQV# sh oETī_xAlVt@U}GIiܙGk9zG,Õz\* \%;4?/nm٧3\q =z .'k%$d!=f5 i C,-X0^So"+kp -ϡϪ Q. Z;yN@A?]ǵ||h]V5: jti:)Dߨ8>XnV3&S@^hv[ og`lA1h"4ƆH s$%WN:tNmK;oHfYN5;cALc64(M\KAknBJWVFK+0z_adg,mRѨaQ^(HpU5wGu)ebYzv<E) N/Y1HyHM*34:Hi $b%o^M7'L @V3N[Se:/,?+5P@!~ߟW"*[zRO]2D"p3\f_ʹANm,J)?& \SX#"`a;: ̤V (SoM{?hIwGJ(hk8j܄73'C3D&۪*H Zz_P64~8K,'\A?zcrީH<ˉA`<\4b(udhGu&7֐Kzȧhn["ǭ;~z 2j-u/ Ę;vgۡe̓+{G˪o0V*Qq0S"AdzM_B]p>NYsa"Plx$"Ms6`@dBBF5挸%ic- ]ZH,!-2==%E3d=;>Q5r] f[L2JĊw@??:\Ψf9[R555oi$G_u}Z% yw/F!:V\^|[eFSZ7qSvx+vnB49B6h+T 7MJ%WITUbXrbD&`Q+~ACb6SjVҜ!gE)Wq+RQܺY68\U(Z-\$,l4]}2"aÀ$xLYĔu (M;RyBk(՛m21uG i֪iMuaWLtVxZVE;oU^<+,#.|T2:=KV9;[:zkk O(M{z zի+xeM:yɌϷ]42񛬔3U􌪹}7H cKk~4HFw}l&G6}Ȩڑp7] eLiNlzx>7GFB2zE,v$sgC"MmZyU3'' ]dQN닅#!7] l(%0؄j Z, #ix/*l-%DY:3aW« Jn%Gr^9FE[HA3 m|~ױrO\gцܿiENj$ h|wQvPZXG H@9e-< ,pgK:͹GU8ki26]cFo"?S/Nͯ=$ެ= @#zm~Mt 3% .w'3d OO9{hpDq{.FNnEsOm8 D,p D("zǗ0V[r{UFLJk}NԚ7!/$HD.6.cu'S"6eQ̓N{\?ſz$EFzoʔHu9F cP CKm`p>!xwUkN:@ H"̪!T8]:qq9P}I\CsD+su.u_*ڂ4,$H+\s9kt_MEɚiFNTGf!=E[ʒ\y X/# gNtM [l呠w-$$m᜽3Nk8x漖Z1f\xSUhmj78S~AvVb^˸@Pvɠ:V")H|@UԴ[gA"jtѾ-ҖR<w}`x['b"-=Y5VfIiᑏPaSK~_Hp%I'~0ތ>$*& /[ `-LO@W%u7s8NS랳OQae ȳx29ïW ɔ(4ct{va.hˊDِ=!udv6O8ՌP^d '23k،jmvHJimѸT_In:r4C1O #Js%Cz7uC<j5vvX.<2>6dBAծJaDѮ" MhBB_(E1Ϡ][Bv..7]ȵz .v<s>s8ڔS(qڎԔb/b+XPE:6?l 'Oٹ=$PhW9A4gye1'R^-Z +##7[_|8%B@zu"b>6t!aR#u4@ޠ}r!Aw*@s "l:wPɀCuLE3Jnm1ӻcES-ǂ(F]JZV/<7k4U󙤎M /T =Janڢ? վ xUc!m\\;nHɥb$̵RгKͪ5Ҟ ?9Z$^^VZ:GN/s5hUD/w>obm@R-`X_}Z[JqKkx;?G7 y9|\z5?_ԕq Gߙ6:f̛pIy0/CR }y\oE^/`%tLԻVj4<#1W-V\HJNm$!Ԣ+j<:dӳ,o }x?_jWG=d8/٩; p L%qڤfvO35 d bGotPZM$_l'qql }[kUBcj G攝a h,36h̝WE ,a ǁMUY„g5Gņʎ,S#yXG eΝE1o_+F~ R떋o8&%(qӜZ{%XufNGy sJw^+f.#*}+c!,DP1{OB·HԹʷUHΤξ:˴Ծ6jքpT;?$< 4õ^.}_bD94-pdAxhm].`P8ٴShNrZA^`ҷХF긓_qdRime~ǫoˡefb vApweo 2ҚDk&/glRBMïX_!$T흍"S 8_0\Mݢ3~XΤfp}ZQI$b;XKsqLꔏMDř4odW ;wrDHt"v gHm ޏF(R21 }9u.vI+"Qg>?|JO%zY^C3j4AZɌ0v0PE;/-P>/>pTX29nI 鐅AK%*b׎swŪp SfSMWk]hR ~vAVS޼YӫRx5dcJ¦N*01i[ijPcEqJoy]{90ػaQ t w&C?xe]ñqՊ֊iXIbutRCXJ56^ -վk{ƣ }MANp?kqxIB%nyb.9]6Q-*k5'[: Vpfg@A!'oTtbS IN4 #!7Fs/)1Nm|9&&=T`yd?4LKm(w/ ^Vq:wWd[Z%XI+GSg@g6g'=]꬧i'ˎ-p'EDAal)+iXn2e)ͮ.e[}sZqKCr{/RW2{6@?0-Bg&!p]GL\_eP2.DR+.>/բL\8n-!=]] d }-bH'Wd(:41TlڣYf%{PY|-^1BWҐ߇ˡ;91EqzXg](cV"x6qh:nv#N74q Gc @ӕ?Snԙ)ٱ3C4H^jiFRKȔ\6Mnsd(Ώaz;; ȴwD[Œ4c;C%*>Yה܄m.dFʝ7'*m/ ҋuGb.Ҋqʖ"љS4iU-G*3^XrH1-ܑ/DFgx2.G[6>_Q*aN#0I󡷇ywPlmbJ\4E`1׏+›H_B/)J/;̰<ퟓ1KZ& @v$ imh²nsp<MrLʗBmMp*Bv`Q kg^`[Axm$_IQ A\r5SINji LxҌ&b&"ŞjD㥓ۊصyV'aGv@Z\%ޟ"4K]&HkgP7Ȑ-IR:[ nyhP<Qr^&Yz7ʅIc eK_G*D6iuLnkreD71ϙuv-.pԤsdڀO2 oZQmЂ[Z#lNZr ސ" a4'AO_b@K]ӑ[04[I4e3>8htUr\BDq0`']aԸLA~-k/s7 ̥Q4C \8 *qz*!,&k+1 N@ӈ}?p+Zo}'ku|ritlTd#F^냢`h3~5_('!6<]dX6'GoT-ɕkbG'cU25O6#M}s_%媙IB:Z,;CdG'oбr{'O uۯaor8,:w<#1\_$Nlr]WNCO8Ŝ͋J@J1;t* U`3D ct>xISx'r%4(kmn4+c v5'Xd"H_Su|_Y)qu+Du:O,eiE;C=SW>Ѿ ΃qŁs]w!OfLvxZH]694;فלV-#'J$U'u3J_P}:bJ ?Kb/Q6}e4<VqR!{z0ɋ{+[JF? #`X*ԩi C!|QSUjDs;( Y: n*w{+,_"g2i<|5=GeQ((eu%x7-+͏egcȑY/y霗%\vo:WJ#t\VlPُ @8)bb?"`9I+у-](ȬSgxr"xK󑫹NUS0|0K3al(\-[" m"6r|IX/&j*Ԙ7D˨ʏvb?u{Dvua2}-.;6mҘnGޏFҌ= jbJg9*&ާp$qTUXQdw+QVmOju_)+ KIqUįSYkϳ)vpo,8I|E^ȅCF$|r5 1Vhm)rN Q83bev,C%:@ĉVXCj*Y͠]#Ч4MZ 7IDȩ?m B5)/GMPf0u懡M t*SvO 7:cߔ{y?q*Ū ^ʮϛ?ANX/51avJMV_3םI3;zj)YȐ[mV)MR@)1I;+y&HFƟH/Hf,j*q$BOΞ/9ix8+~{O 7 P -Jn'r޳f +WwR`Qp(\B MWR?VcXkr{ \&s8i&ֈ=&VD".'ÀyK,jefUr$% yL̬ yC ~vTU'ӧ|nygD%bX8h_8YkZQ;[Ðn"6| +F*j|zXs&=(nׂ)U4._j m836T~qaO-k 9#ъoZr;dhXo>,p9~,YBle8=Qsq">..@=[2"~lW !\_TmŻBTBΔ ۣO5>P ~g]Pȹ뺶Ȥ,0}rdtaF |€V9#qdƅ3S6:5)zB'/ V-f\_(FdА%8^hZ{l٦y^C ˛pϛ hI/}&Pצ6h3k~,Y?#޳ pp׈E*6Ka%:}"ִH&)vdS:rgI=#_(p&hMSˊXe/)3x-Ksm!ɛ֏60ҿuĀ< @Ŝ+DO jMSnFImafI޲.wxa養no=ZnGYpLjšuI/@oE'l`i7L#g_0Щ2b&Ysz3~m+3y0;lZB4&(!T\XTUP=yvWfCJLeKl'9-{xcá /3 >XoԨPu{*xs t  ZڊĈ62eh~=dQM(ny𝓻Qq]PAH]2Hl/M)P,$|[[/@] Gr cI$(B=.7x iK֞x> PDei~'&+1;4Am| "}O!/7f~6"VgɸЦ?LÏ4CgEQc֏Q2OY(`v_-mibr|:qs 4!hoK~jQe .)SB,0p̐HkuGb€C}5j-5iʞ~6 ~8QAoj-~GpQ1"TTLqؽ~x)x P\GEzY3o#N}GC{ MqҗW|Oq%hV*3/YN, n3q0R7 n:msJ}Q0Oʜ A6ky^W$#Pms ;Q= OZۛ0fF'dL4q͞ #]o $&i@g$id)]+FZ60C-|@¡@3zwa&mӋmzyK826,T$zC"U}J)bi|~vVr-Y]ފU@ %UݨKt;۸띻zJP``~~{_M"|Cі=񍐨lg:R>jN A|僵  `\'$bc ȣ![*t FOaGx8 l~8cf?bTqY"\"4{%;}/ƝZ9zuNRP$:}SvuTU Vwcμ.oP4K4z\Gm T~qe!9Ą[NxGg9ۘq VFr.ǽp9]'CAWrAߨ9X{㢲d8ä'dFy:ԞfCɏ'gN4 D7vPLxDvtq2o5j$Kw<)uY" TCZEo 2m1u~u>z3pޠ,:14,ZNm^''>!Yd0&‡ b1Yb{ lvlwydNT_ղT > +Hu-+D8( ;]Fx{<3%2GlyW ͡ȋ%:Y6ҖoHGR{/AvsC*BUȻB c0fc_^jΛ߸l$_?Be$<߶H1eO{lXyg=oƗvп rזfHE|VWp;=WڨKO|ŲJt;%1SٕK[ zWzs0G0 ҰiǑy-dXPu {zGH=#Y8JŸeUQbm_4s%^gh#,©~ !M6 lDiEAAos%J=WmFi&:l/ L0΍]{;^L/6Oq;$mE2٦41mJ"$xSRNy< ג'*uؾ[+,$3 C^Mߌ7 Ui t qYiͫՅBA{QNDY]8Gt9Zܜ&UqVW;U`x㙎Ĝ䍛+]_; Yh0c3R g:Oj0K3TFs\J^kIw~;3,;O9e^ҕޜ睶r3CrA\6˙dgIӃx/\12-5Y>f'w0# X|;_U m匀NDAKAd:9`B )9aX70 >'gT6l8ky܂W(rL&cVJv*ɟA$٢۞.?*"=2\ö拦1Srrě&v3?b4:*ʡQU }3x1W'~Rnb]HnuJk; 3srى@gmo&Sb,cy/I۫5BqIL?g5yD`vjmc2ۂ̀{aܚ.{hdʍdn/3c&~,rl=N}%.3Շ~kh'f5MdFHH" HXx2GaR&|R{0^;5`nwM ::U>AcKtW J tt+vΗ$FQ~ g/\8Ұ?U;@6l9ۯl F4p^ɡwOЅE۶E *p-bdW-VV%>b!4+ ߋ$54*C7(IבO@{Np-8~])X4`?\@ִ96H`gTPAA+RaP;Y̌Z X˜4u#])Z=4!-8CLg 4</(6>=j:boc熋ҳ +t82sc(^JZ^^ @gTvgoB.#m\I2h3x 9Nej`Xa"0*qaÊ}kC ȄY7fuUdl8i*0БڟkX;IXT'9i$[7#qOjff{H}2({ 92 ?IMWlQI S.3CL1{$FG@Y`O[l dbsx-! 5(^e[_oLP7#%9~wv (" ȟ-S lףFVl":T#no"qTyuj0q|Gw0CZ0sfк'iD*~lUɷ첾~N>J3:Ru. .Y Q25WxFQ`W0oi=)IxXA+V伜D}~ƞKYxxeOz׸tijPiVYmQ=A*x}K$'F` K+r[4\` lXo%S\8U`@Qm!2=JHbPu!ѡv0I|օ,ǎKqkUV8n+4:8q 6513N(2~H0M*+/mY endstream endobj 366 0 obj << /Length1 1643 /Length2 8978 /Length3 0 /Length 10048 /Filter /FlateDecode >> stream xڍT.S܋P[ VZ,@ kqS\ׁRZ)ŵȣ3ssZI-ozZuMvI3;̉(Vqq@ 7&=^w m2`8;@'/@2`@d8bKٻáNed g3 i CM0 bXlд3BI^ՕlacfB, #1 5&=@]  6PS1f4j_>?tg"LڃaPj)s890_`G| j6y s0@NR~MP{'GGͯ9<,Lsr՟ 1}/ӟ8AN?޶-\~8m~C. /f#fydTٿ2[ >Vs#ݿ܏8܏?_m ?8neA nS;S ڠjIJWϢ)v\D檌edP7uYs;zЖ֟^FqV̹/O %1ص$t;sp\{JGBf65^aݖNGiGOdN=Gsb~Bx7u~1I=@NJ=eioZ܎/ߐQ#3xJ$)z.GھaT]wO DŽ8E.(ƣd}UZvS7nW̫uRɪ  FQ_VdCW¢BNu+OG?E8J-4UmRU>̍4!*~‚C3 'W:#˶Lӡ T h춟Twgw78 J=$LfHܩ e=CWg\:Ո1BEKS3eﬧ-4HvMIor@!u |_Kq2`2$Rg,;)E\rO Ue\?Ffgpk(1#S] sȢPc;IK?:.E:5Mor=OMVYBs~d۪11ެ0pyۛX⅓^GAQ,U[UOqՈ뤕LM0 JM-4Ms}+EF/# "?ZX`Q&i9k`pS5ޘ7$ xbCɢqÍRcsiDOJnUB][_Ac>U_vvmoH;| u:l)eo{1ZˉӨ{DPxI5/Nm-%DI+jǝ$XF_~!)i"P tWE̗s[,rk'|tKeoF.~cBof\IS=)|Է _cѼYq>K[iSghµ=j-ˤ&$ehcn?=SqL 2OEuEg 'K%[)^R p'SՁoR$y$Hx8DFi&,'Y]F_{Ԋ!yI C-`1!w2;~u A~chn-qi4I#mch6APe|!Qz_O0%\V'ahEN,PLoI-(~Cn eeLTٴ% T򖖡(j-=Vu]sp ,18K j{6,B6vw ګΝ|k)3)AgVv3\*0oV!KM!2%L )w#/ $qOEђxOoH8E9[W$fz%qxߛ^ѦLB" _!$.|ִ3#>S$//[had`Ճ c$[]R*E2ok6AYzHz/bءa'b ͔In#܎kjs]Ļ@>p;t{V +79gN1_ q:ϻ3PʥҺ\8TqLz}XNnL+]zT?*%W13V˝prFxfU͡pJPF*9Qӗ)o&92ܐ_@ְ,xּfP#!#ӤR;#E'5ejcJiumbń.ٓ,茊NMBft') [3vt߰4 R>3"x7:!NI<ޖ%0xBb!Z#:J!M^MH:fCcXM+&{+S#RCO1Kh K) 0L\>:z@_u<'WzU f3bK: oșˡ_M]d6/* {ϲk$ ֒yAxݰ 6 +#ͳ[tYM[uj|JIkh5zޚaǯ%LH}k^h>xAߵ+;^GR1L/RTSW~ǥ,U]6FHQpyzsDmOJ $#|~DHsp~"W S㐀'Sb|n:p_ yh H kn*-kѰzߪVMO(EyՎתI{ME6E(Ȁއd[kd(knp@j"{rފmaz TYQf)hҙ{땕 fHDlگG}((.sm- :Skm[aa&+3A'ø#I84-Qmo\ "Y k{(F@ti$Q'_  u]ڨ#?iͮ 6(Uju`a kWR#?Zt0lIC|\N"5 #{)t.ozc62(x$&Mղb}cb,2Wb;"ޑv(FPH'N4vȽT] 򏣆SE; ȯ&t0gZ=Syq$MgcMG= W;8Mw2%g]&1U: 2"3, 9\O Q 0{oؼ Я$CE4?[ qu?tLKYE=|\t>4<{Wk<϶9ud;PE,_̈H 4LN)1Q>Yᕮ5R^eβM>b]`(uawdT >M +†o(IgSС tmggG%݆o=@RIR 0> 5gLs?$aMT䇈lj|Ȳu7CG^ o,Qߦ6F9ZKZH | Բ#cH8ҏ\o,\V_[`߉Lbd`)6^[~2 uKΣ$5gU) 5f"Mu2zZs5'ߘu26Ik\BaFcNuKQ?UEd%}1˽5J rhLe ;``ʺ?T8|PB ln ̝GY<>mV-~t^4Y5cɨaѯ[yKqe`G7~E'5{ǒpS@vdBNj&Xma=,5wi] ɞ{^*FvHʕ*|[*7^}=>'~zҷ56'LvmhS6x]fX"}^ȼv!˒j~f­ g^-CA%wea \dUG`h&?2 j&ZT_<%쾵C]ĉ(Tɬ6(Ku"R/"vjG'ʆc{5#ʺjs0 hIOqw--:[s~ kntM@o; qQs$+ +o n&Rf"U녆lx=.ӛsXJq5,"5HH*#ޯ.^Hr?_sAÐ={ȁyj %z6%Id ѫm1މwLVoH&XCYyZݧ%JHP3HMxG 21}hma1ׇs Uek%""ꀉØǩ3Әx7,k>Ƶi y/B@+~tk %j2nW5gՁ+\k7ӈ.rnj1yONf}95XI%#{ :3MtsiT I=?1m61`2Q;pSjͩ(q;k# OvרtgO+<P 5!MV.ü3e7?H(8j?(2$g@^1k^ig`Ohog,Ɨr>qc>k鑽NQc ׸(p- /̜y/2,[SL!rJ< 3>moB8Y;CO&` R(U9ª́#ZHxXd,4)u`V $JJFW`$:V[E}y̏wOvC#=,%Rl"j&]g9zjt:s`bb5 ) xIDĒ, F#<{G'*xGQ9nog8Bl}x]A4|e4 65(_}m%Hi}ݻu} yCejr1^Zr(Y1Cd7KWnܧU겮+dQIZo?m"χ8-ʕ%3%E2\63oj&mغ<aZ((iFEga=~QwFgtsQOPMI|N<_riY"<$Վ+ppÕP>cDއf2 *\,m]f1ǹ0ʎ5HmUl^:>UdsRG+mvxh}O'dFhC4]5:mfwS:ٟ6t܅ή{x}7~e`,s=rh]-<{ΔVIC6Y| @ZSY^!ݔ;%`q͡7Y8-D(Bm̱/AZc(PfcL .'YX!IDz;eg{2Ъv35Z,J}&Y ce1:{,N`ЂKQ^}P!R;BGXOִfEC)$n,@na~_ΞGB|Dw,勞Jw9?|5Toѣw-BZAMUЖ'i{9dA#c)W܀*{K+zM f j|BE*1ݑqi-#!67\'Lp+f o`y \n ekueU~=5],3yM1&2 & cB9ψ[l1(-Ij:eL4KmIP}})ߠi=!1g+Ȅ*¹NQ10*@ʺvk:}Zt󆶖cM8''4#^ludkb EܶAOɖm)_(~7a u;j+  !7C?P"i:~k -v}<5ϺھHqtҟvɨِFà^ZJdϊY(vvxnW;Xkס^qJtv̳w(_m/郸ȂE-v{zaL9R3cm\/V.m\$K㓮"dK_Z?=UESoHEK›j/nUOexr5jǕnccor4z!aZܥd-?DMXCL\O2:XNg*=Uxv%GW!hgG>}YsXlH-Pu wk9XW|?0ڑr0w-F8kSȉ,_ a~ckղs nmKFium<}"ЬXXe`]E=]RyNJ5 T @Ӭz:GjgOvLL.En_TM`@4+|!z% %v91ϧ&*UBMviU]b> stream xڍw4k׶>zgF5z Fޢ"q^CD !BFF>9=}k֚yn}]Z'EYÕQHX$(hA$" rp#0pw4;)0W@ Pp"X\ ,! / ] Py"l A@ 9P>{Un HJ.pw h0p60g@ec|U[qQ<pwO-se@ !@Ca`pጰ#W)H[;pSQ\?߇+fcrq!}H{ 1~ 9QW0Of}5: P qGbЂh~:f%Ġ Χp\uB~Yv5l=\ npߘ+?>{8@ m~6q vsEvWkvB?4`=~E ` G ~i_ݿ;0] ~~~2b-  +kAyy7' @$ @hT 915o pZ+xn\}l#*+{"eg_q?Op"J+) jSp[GU0+1@$j#06OJsF (4@}E_!zR i)3a1q!]qIXL _ƀ J P?T\ t$!, @HoKRekw+"tٿD{mFP6aa 'Pz/Ox3sO;0_sot-I-:6kNRX>o3٢%nm0Zr%\K,-oD/@%ոc>hlFGn⡯;v "X!g4.TWFgGsr҈۴,Y=|P};R(Nj7A`vy=w6F jkc$P<^w/IA-8EcuqIzEFw O*`瑃&#pxXHIu+N'rI8TqqO!?IKs{AXHaY2;e P `i5 kh(6hp(`<(Oa|F@Ȱ$V  R=\['*9}$vH`qvap7UEuz]˅)T!c N~k ei{XCP)+_S3jQE%%Y۟ rBTSB)j-K|7H'?vum ˞xڐ< p݂&pXw^Fvm&Y!Z$8E6FWF3]Zc?y>9plp˝x Yy:guı_9wk0ʻ]b^/\_fØjm'G |^c+U,:HӄijЗoUެ8m}a,%\ %(h>#G?BqBs`X1K!o 4yNO HNvցISq̶S̸[!^s&/PH~dnWzʢ(IJp>sL@S/>QՠY=cIX(;Cg$z</"%L1w=*`G;tϾ5dYWfiz#-hH(lDOlגk)n(*^N,e\]ה7f^?eڷXWShpw[~$Rud`v1cu@StA`<&/ ]OMF6fRò3tW ug|.EcT }`3蹊 JhꌭӵV9ǸˏM_|ZQ1k4ff[Io‰@+K͕]Z\JOH53R ̞'*lo/ <*q/7Lbr&5$Bv X/򕛫(J47#XxN-FDg\ U Ua>Sz| 9', c۽u~_\^L9di/> GFOm(iV_"J-ߒ$d 7rZ,Q)!6Wi߿ |}׻WQj -%Q8ܓy{]1[ 8Ƚ!*_sGd7SX,lUKK*>;if#9Ȕ~܊CqpU! tՒa ag=/=݌%cO#w F$/)E9)HZwKPI_)rp%|zA@YQٟ <1qh~BQvX) zCmγ*V5TU*)jVN^li#f''e@R< !Ӗ7.f<0'k.`3;ȶ[m^_V1@8IdXIh`cOCXД4ĝşUH:(FQ@Q ՗>b%cP7׼xni0ovYƳw-/8ڎg S,3Jl U HTz-N:S2c*dA.zi./[/Gnd)Tݤ-nKoWl#W3~:,L<F|!0|M}:4MP`|YmQVmz@G yY_$VZ oDt]wO/o,RA0nzԿw?a:-:\/\a,VAW% 7o#Q K|XénCxV ]==%l2?Zq@:1w ٍeZWIzD9P=%/&:Z4D>bq33Gs|qL ࠬalt#fGDF]!pGe-4\ NF>.#_>VK!^"2(Ul}OE*n'3hoMzv\w6Sy'91IsZxĚGόׇSG*M Sx$ِ : \Ns>KUJx!4=0@$hUk:z. GM%v%޴7h~?H&KX (o%f^,-ݴ( u+gZO,P+%~m5րeLN1yP$ n"$Pwj0h|`Jy=kuD5}%ٙb0hY{8~E5Rm :$l@C`Ĵ^w<|\jTi™_o l{H̾}yR'9sm[E*7[ƈ v "y~AҰZ˒u9ə7ʷ2Bi!k'fj)>Xs𐿁mfy 4M/qzۓ~52鸢 u]ïڟE-q{B'[_3bQbvBuq0>yvۣ/-.Myaɛ{/w״RMW39DȔH*iV Ϝ˻̵9T@9p03NI>%?.y=jN@Oq ԦR'v]yG!(p"Sض6x3qWْasԨޜچxkTzemmSDZϝ;rg#;{jmy~F# ]h7T]EQҎ(>:~Z5v B,Xҋ}Cd~&NKIy5#Z|By4ً0Ѣ 0CrZf5X*Fh54*lDpvݴhR ]smFE02_ϲP\²by#@|J@Bѧf}s3kwœou#5! ÏG߻TEywr)u?.*{ްע)wQ8j 5[́% ;z &-C+g=*$&$/dQNNح٘0dN^ƤFmȝQ ;EPFcų?",Ř50Ya߸֟[Mq8Z^ğ{m)Fʯ9щPC3y"`Ri[3YVWqSRЗT;^ 8FUu(G=5=爯2KΧس_W0 E|2Ĺh>;EΫnаڽOЪ+m GNX[Ejn!1L!PYIG-̓CG=DKƲ?jF Ug-3<;G<. c~m5{MD~KIX1w)0! l(#sIM]}>t3㓹qE;wV5"<Ƨh{r* 8+HE"MD"}փ觬c)dS;d[\8:#r3 r5vY_K$)zTsmQ)yL58ݨ]`Wm endstream endobj 370 0 obj << /Length1 1412 /Length2 6212 /Length3 0 /Length 7176 /Filter /FlateDecode >> stream xڍt4mn -JΣQf J13c ѣD D5:!z%^7wZYg}v3?啳FYQH,/$(hEH `gC`a `' ) {S`/($lXT@ (qAX| s"`W@0_N(90( h@0ˎP="`XbO>8806\v@W@>``!.S0 pUU0_`<|;i0@KY H_@2AC.Grr?9A14։ akG_e.Y irp!NSD``swsvHSZͯD8:T`.]l`X@#sj熆/w@r #p,`V0n/1WtI?0KYn_1ʽ?+'(/r$ {\Q_w*" rDE~,Q_?Pg RIr9o`0( *I9+7>/42nrzت?v.}4bh̓{xլ9w.F%{E}I~7dh Z"ۉf 41m83@Eph5iD|pIcar B=f8g=Uk!#P%=]{[E _ܦpWcNꤡLTr!/|off3q~슯$;$ںG/N%Ix4!s)C]ϖ|]OL{n1glPq5 ZtWL=kA٤-: '܌J:| ݣ L%~9{¢ݼ!?]FF;mlhdDA֟$K]h`饟,+>Fћg:h) , ҍ)\iFcg.߱v8p%$vo:ԧ^Cϒ?ҏT9Y5LY$d8Tj:-lBRo>y9lsWJWR (1k9JR2 &?k6HqRbēJQ?b0?5+ȿI/p=ڪC&z}wE1&YF[ N} uO cJ08x*|ֻsYGYtmh A?uaqY>- .i9{+LǖLNgΈ_8n hNd'/6(8WV55IUJkn"RW{^"EnO3#K$Yk)Wdﲐ/sB30{L15y%_ W3DwܰDKYI;*gw'2 9΄cU+ #.ǾT?"7v`vܧ!]lT._bZwdЙ{g|{<ZEǹC^H<Όdwd2*eBme(SoM}x)VXnH\0&㉗?\S=fQ"'*p]4#c8D_6lh2m:OyHg 3tE,NfʒrzO[G5m.CcÎK_'}>>\8ssފќ^p&)y[{防q ^.fIDr?capi4gfttx/;sa1C86-&RKm8H@&AwY nUn!2pK=՞y`fٞ6lIGyD*)7FGV)j 4(C 9irsF-|)6Yii IM{]A85`3Ww˧ql0n6=XLJ_4,ԿE:q`j^2]VUQMBovڌ'0Zcb\W)rVF{&;2o;hshvR7S J|e}eBZ5#6XCPS?*lʝ/+ܚ8' 6jť5&0 P8\!`6.ߴb!7'6oLVp/d6fet|]6Gg'źI͌wګM$xM 1C rҒ;ф'DWCLQoƚoEp5{YH4nF w9% TE<:FT&g+YA.d#YlA3k9IEѲ/&n:6NphA{hx Uw=KVݰ!au3sRcQ[7Nd9d6 1"/tB3)W.(G?r8>crVQl|uoTʏ@gE4@YA9)s{rqDM< ?_O">]t&L@,nTۼTK)OjOPYW Ч.Dmq,^)-a~ciE.)e΋<]u7X:Qg\3OS4О*=B%ck`V%>b"+n0[{cڼBu=FƌmzD,ר+$c{4㢈>XTSO7QaLpe }S?[!7HFf6# Nth?i> *͇*-}N*:\uUi%%w_Dj PjnV6%_+Tbcڴ&Idh8+nt+^ky3\3᪤Tt6Dv۪`ڰ OdgU{Q -# uE1 S<23R|ν;lnEo|%:u9Z JUA8xA8U}9qo850R7|h̞HV4-K[^v`Ʊx:1AQk"Y~M+iHK[]EY4d/^a]soYGok[ [t M |)ͬ7+42(}!{ r\(fbdٖ/b"7w[fXU5XS+.>) @OLѲ{+$I}e%I:QS8WGi r'Q:p}NB/1l:U7n\vP/}\O=d zs>Gm[fL~~bC]}J.~ga/#];UQ22iS[1BuDyҊk1[%\ oTe j(_#fUMn:L[@n+H [oof2zkV Y&*MG,up7rwҹAL1=aBE)y4Z*3VeRld7ZtU$+8vSLm|^àwt,[r˸wʽF_E\P:=5xJxag(%y̥ dMIٺo^Toϧ~*G:ĺ`ޑeNO.Dq,yGP(DjPDFlIӎw`;Riq+uǵC:|^2doG9t/]L`9BaS g?VJ{t{ތ{H\  kJ=9uDo7FNBɯ-UDsۋ2{{QډszO a؃[g ?]h}*k>ppBJ\=x\1`@;ss_m{ jnqF*?RQ]ʌfwuS׬.`=lKt mG\oM5(+ _{tG)TM)GyK2Lۡu+Q5H yWRhnRBc ~iVf DuO1A rF;5ubjmg#OZm7y2ÏXi_6+Gs!m,Ddd{bQQ 7 ~JXWgRO0_PQieq{Ec("ZAj$ӢcMUq ǷcI r0Is)݀$Jdjb?{I1~,\Qy6kܩSa {灰< S̹) Pʻ⫞qQ8D{Ov,p\HVxw&wsc@>NalDFy?H][Vo*O~("ȗ§ әPNuSB${Ą&)5"Goe;S]C@3M7z_5ZI-iJA*!8tq j&|dqYgOdg3 +Z\WZͣHu"g]*/|0< sx|5ti1GwjZ>ͯ/M;яeGE-8+Mw:=&"#|'"Otm-ga4hH.K=x-/1ң\N;ЯL7Cx09ӳ5SmwI \zK3ꅹUA\R~d)y]];Ew9L4/O}E*c2quRv)^qlsEjӏ?V=ݪĊ'L 7D蒶fEVðJzqL~&sLla~UOmo!? yxPehfk=2ɜ/>[#]8oi{F"\>#;fd#AtOWGXF֖`g0UD_͘ [v>@w|}uhp\>LW<'PeAWṔkFhW׻[;h5cù2ʂ/)R=883&j;bΛ!Jdy(ªaJxH7_1^Ƭ8q7\2{[(0$TPrhƽmQwemy?4Eޠ׋xI?Av &&v4ϛ;hF-u0I:qi3 lۢaz!!e-N~:Y V\ΛW>x5<Щ`sFʔ(d6!ʻ]Ѩd~qcl]Z(rU-\k!\|@P2zߋhk^uVr:ޚSѱRN:+aOno_ MJJ{$-%V+XXEXn5obb.!=lCo55ZQJ]ovh6猤nw/ϡpTvxK's> stream xڍx4\ڶ Zhчu{NDc0D{ !: G'JM=5k}_w{3ks (!mHZ$(i PD&0пD&Pw ;ؔh P<@"H\ Re'-xD@QDJHWw#S[7Pp `@v`*Bp!}#*%$%vA "dy^0#{BF]F$9P9 h/;1a( @AC -+X/?@+ ; ]\`CZho4?QHL< m1߭ 0f? 0W4JQW6 씐..PE?e;w?@z!^vưp2F<0ѿmP4@()&& @PoЯF>f ~HW=f h"C=4?\@; :DΎ1CZc b}uga7 *_NEE7O@X )@ 1( y?}G ]>ݲptB&#(tc,+#U8/>z1*Fbo)/jC`.@1jP@8`-'RyC`h_nKopD~=a0Q@0"8c"( 5 %6aQ1C9kJ¨!A `f #݉~,H d# 1vxc Bg0Wa5 ^#x+щ}Qhg~Zjn6v/ g͕GL3~9~ԝR[W<(~eC;gDS$3XZp\l*Dm?JNYq yOYWCo[dzi]a?ş)}YZZzߗ!rT[-"kpuR&RyZ"]K2[5z!Egdz7r iNv`W䨶:<0:egZ CEe'-d+]M.(9(4ptXV= ~{4cIA|j~>ydD=h֑f'IC= Vse͓0h֩&P^rn-DbHk+S\*\!hK9˅*wdBe$o(%׮~*PHJ3MDXn'EC|şƁ֔e}poP5nSx{\i CjRlaV~hc~VI3{;Ɠ 3{Vt\T)*nqͅK%2 T&&q<"<ro$ڀi.Z?iPs=<>9nEF_V֚bL<=ұZ˩fZW( ͨ{κ2?+geKzH'˴[xI p,X.F>M=BXͲ7qnYRH6P-aaBkkV<٬Fo$ثU_Vi; >]zXaF)e)P,z" ^d%ԎOSZ EMO=ă޸*G]S*HtC9P+>ZfWuhB66L6FBٖ'lLGrk(R<$ V8zg݉ g-bgu?uox1K:P4'J+ήRARaUK9jJrl!Хb/O#bqzطZ; vīܝTlUջ HVK5> r!v-pxK7ZcO cHy쪘-Nx2.B}FڊsnQZk6b:?-G -՚QRr9s1m0:wb@]g^}1+9r ՞KXV(gK(r^=:Va>O)S+1jeUpM{S*e|}jl~\#̴%]wzj#|>5Nj> oVO*Peߵ1EjdATUfԖ4vT(5][ -O(/r^|'(Kō~Wnߎ=ޖ*v1ʎ m0qXs+}4Dtt05ͶM(@7ǭI}7KLcO NNx#|W_{5rZqDȓ+xP=9ilxp, ȯEt?K Y8QϑXS7b )y' l4,ܺ6OG4Їe%LsZ3 $q]B\r6 _+8d߯}3_ !}x~`IWVNRo\%:9j73eIF^;YF>(W9a as5espĹGstCrnYDHkO%zWsrbXW5@e=ࠫ ǐ\;yH}:44v:c/}-Tz2m!=xjszuu-q5wh|' O^߰UVFz<yHS乺uhꗫ=,"7W}d]ʔN“%1<ٚ0EdR3AU i|V(&Ωd E_ie}d#x׃mp:[tk \}%i72jcpdاjҚ'☥I_|0LK>NAhP*` PvTmhrBf7+~7l;K1%'yA@QB:*⦪/cQ!/g}7ޤ*^;!}Ǟ_C8mIbcN"/cOssۉ] :Vc9j٧N\} `HNw~rli#}-BvH;)d[jP+wN0fC$nyߧm2J\T4_F_3c#ܔhҀcΠ7W3m۶@.>9Aђ(NY8PCu[J:gh; 'uVr(X+.lz\\Xށ=lV S M.u}W:ZH;E,y,q]h.4;jI>C <,X >ˣXBtS{=1ǎ3O?t<ŚF喝'=2ɵl9)1OcGğH+Z'薧|U%RhaoZ'ݹηŃyOĒz&$@@Z_Mv4(On{`pg3eLa綝J#|{hƞ(?u84B?Kd æaLRsOxcMȎ--vn#Ѻ+%.eMf]|Hny/Kǻ D<ts<'ox^l5/_ 痨lܰKfDžE3 lenvFm^$Uoo]^~Ы.=K?h+^gNh=)_ @wccV[fǗ!jVE8V/SLg 5&\ԓyKGp0~exՖ6[ϱ=d|#$}#ƊY#!ZM>v Zpk{Zeز`nlڪY8Y4gޒɚdzU臯so2jm'Կz-(i,3+(2dف3X-HVAյ&g4>QcLɫwf@iT1&Ǒ |>/gtC8 D̺5Kk#5;bOwE5 &=wNUYT#,5ƥIM,(E|A$6QA,iѪeU-ϳ橧aiի ӊUo> @\ـ%hz|MB[Эi2::sPӅ endstream endobj 374 0 obj << /Length1 1984 /Length2 15922 /Length3 0 /Length 17138 /Filter /FlateDecode >> stream xڌP[Ҁ!=Kppww]ww zسg&{{)}{Pڑ ,,`ddgdd##S6sGG w0=PC&a(cc d `b0}bbbd032rƞ lf|: غٛ:~'Ґ N/wP h ho P14:OJSGG[.z}+z{>*Z)@wj owkpdeS3J6Ǝ.@hdm|d(IJlKm@ 2 wtu[eo`ofoab ݟ_=2Em֎p'bf48w7_6lT윀"ljhWe7[࿔L?z򰵱23~pw흀^T/11 @3k??@\?Ə ?&ADPJ^N-W)$d ca11Y^G^uWw=^*ƒ\ Ϡk11~b&o+H[׾YYmza>c ->^ CWecfзwbx0}l_ `qp|0bM_ B 8 %vFb0!)>b! _  #A}T/q~C:?3?z?cQ͜HL"Gͦn?XGVc0?j~b(?d8AG.#?#&?:p]c m k\&Y? ^8( <^G, vGMy=m Vern/ [`VX yLx fpuT0+w[Z\^K 2Kƿ۠ԗ,&<}xA+`Dj7Q^[1N򌑪7u N5Vl"4)LqiU"ek$؋w%8kLA 3L*7lp&SHB9*Eoo/LA;ܵŐ1$sW'%(e4}K㘙~pкď{(:=^\,J5*8ZQ`,`(z  [jgYlt="/q >}ynzG)׉g3 =C |3UH((u i<[35pZ/\[t3nAlQB ʳhx-r2baQ~C?c[5akhj"#'(Ff1.*-iS_ݴav@(y Z%Ǩ2\~&b4V ,󘵥莎RxN҄9'%B{䭐3MÀo]~pl^U,l!c<i~jѯ*N+s0 uF_JErŽ${iq4UEA!z4/.p+x>d+lY-bGmNܖŦfEyk[<3>iwEcA)6/ Fujslluu8ǃ ۷c) ϭ\LQM`D8_FB~04Z)tKޅֻ)񏹳딌ށ7f2~UgF|yyޤ6I%%?>֭u=Rtp,`;+(\x_'; %j 3%bh|U_İ/! G#=nE{ r$1!4҂ Cx0K~:0Dž <͹g[r)ZWmZ_]QEUdLD C9ujĶN/}E{j4Jx̦O[:ERs]H&Bus"Dq p;\/]KQ }R[Û0\`B#]j4$#(ӒNqhn4D#~Ach*v U:-na8quQ Y* 5,,hzH:Dɖq"as4PX]6SnAЙ?"9!gml%V`}2cijvOCb_#QICLE0\5f d&tX+V0k,4Mi#wTt*w~k"^GЖ+p9YxJs'hc0L}vp-wE;r@K9},NYupL&;p|"ΐ}VQBoN+鎵 n2bUbI\'F]u>846xT=lrI&5*`7]D#e>gZCh¤H5$jJfy*+%GJJ]kM CΛ n" T/vBSpטgGCeoSg½ wX>u10D:PQ頕Bܙݕ&Gd0a(,('+c MX jY-zi~yJzN k|M&ΡjU!/ԜmI6IZ֫Y,l˺YF]>URu3-JVeXzyu&kt,.7*K]D)L*bs ?%ZꊻdNyJ@﹔kUzzA.@g=գ OP -ߋIE7z̰ D5w񹰕D*NcH[E^U;$3䬈Gr n ̱Xa<CN`c"vm(d&*)MS$+{q[ٜ]6]X ]Z;9|oe.x|z/ic#b"˳r0yl.|ذl upm#;W#0en m?C \w+bA<ݼ #hQy5(cQZBdAeM9a uMBX==Őˤ8R'#%t0q+&bHrY+GvT6bp,^%=1S'R Vz3K9s$NߘrPSPmZP^x'*lífT&H=?-5ȃNqqC);G>MteNn(`-mApB -+\A;8G<({qɊ儙62t,-ZbM` zOnrm4[lO$X"dt'a=9f؛(ӯ$T|L=9zٲt7}Jއ@4X8?#4Ģ`&B`I[I ?n`?N;`s=7T7avNȣ8`i2SyM_5, ŷx^@b鋟:C #T,rggZ RI\IuKlNP7aȍݿk JA'E!@FQڞ0v1F"Eǭ M qnKV3>cA)lx8x~Nԉ$дm(L^ i<&b"=\x}2\o`$7ΐ q RDs@p ^>BQAX;| XG42!]zUHxVW r>njVLûk{+MPmmqVy-0Z4&shfHXG34 eD>IQ.,O`$X^=8~-Hl1O96v8Gó9er;^dm*ʢ'PCboS$+?\3TQZ.)vV)UoH.)`/\շ y:P8>?"AX9hx1{:z bqbJQL]+m= + ﷪eJ*ltL>=7D֥cu>/' /$з*"+܈^yٛ33ӛow'-êtt:cK2{V=:UHHI^Tyr/r92vd@lp1ĽAx4|lB.i!2*(k=eu )ALS;Ǽg,x\eMɫ@6ځRYGف%3 v)gN8~q,tSnGU A'4pLsYv7%tReJ[ 9C]h G\ke|hhgcY-2Z/R<}Q9OQ>$C1} q<*gVݲ d6v;Lyc4Qs惨1Qی{+ fBV*v (D@k7;hFeaBTPw»Z =M^s-hKCVÂV9rBw|o-?{"Z<"ܥŗpJbA#uer%>~^qHT)&Ǘ @ȘdXH?摡CrWpw^;,F9ϧHF=@lEចI0f0@B 4߲S6 @}.)D. Dc[cU<2fq5x>0$IgW{Ot3Rfe\ME2|kRXїgr*C~@nen˶c6> 3;8ޘ:-Ky>|^YK 9 _Yg 9[%aН8aULvgeO@GK[p/?1j"3v^E߷巗5O;U֠H!RUlIS_ĽePXF-v\ m⊝8Iɪ=V?ۣ p!nVX˭!0۽K3,ы&t"UH akP: Yo ȷW"GR]~,h:؈`~}dlbep:ۇ~N NntGdp󮞴o;d`YoڿNʧ?STeK:݁!}N$'=3Xb.[.a'¨[ESAG6AU'p%jA+)_(yuRRzPSJ\>L2ARlÝ3;,FĔ5nV |ƠF7y$ڍ@3&<]]~}l M{>C ߪ.#{@ !sFjěf]yEղȤ2q>*zvE^sSHOM 3 $9gi.aeY9i@&yƋpvvIyySTy`k\:/n^;|XPϝfytYTudsͽ{ჼ\FICKmdXKn}Me#=c>$+9{;]Ǻ4EdS@]-_ i6rîz<\S_mpV9KsIx_0#%QC}BZ,?[k1LSMbƪ"'~j+|YmkУ!ݘy F# M.Dd q DTbAP,vDj2YgF^ɩ EքoBï1NV.L-ϯle n`WE"yCOzy7oY-$*r1}qwɻ_8P a;g'ƻeHh5H%44{ {Ay_m:(UӼ 4"])L:)`D:F2$NQJSϋacI> Ih3 N=21!*Qz c#6߼}Qdu*Rfܯi/]hYZiP9)bV:oDun92|ݿ%b~L4I9ev*Ab)ej7iI;@fEd%D1)r%2]`0̪tL(?)c{uٳɫIjo=q },aJR~(VgbtЎ|3~uPu&0]6|JlNy(2UģoْXݍxXO|T_$.h*Ob@">󀯃w=rr`"B=Zd(}[QHLݭu)WqRJ0nS[9Y#e' 5-~ b*`G;3|NN&7MK5ƫ!Q3Hz>VbżB) r7$GSowi~Ã'/ ]OFɭ rh7|> P<'zګ aBסנ d˘p=܊*+%&AbY!4UL r*{ٝ:R<8DLU39//zY "C/}Gt8sc>>Pi8"bM:jfUYD~,p0n:d;0xhI4ڞ(vupfAT!) e(Gk$jT6?Tbl2&Ӻ!2`pѳ^"؛ԛs_1:<i({_5O!*tr+j= Cf(]bV oTSolۯqa+SS>OyuGBO-Q_._9Ovv݉~F-FN@$[h=-2d=W'" n<3:7l*TVHg}Vd,ä>`&]#I{^1F LuLEDyBdgfL# ?:kLKpkiOĤje סD4`?&wXmtSY '3H54GUүW}z/LmŤVFT4B`m<)"#f}w躣;}T\~[雔cXjTy򂮶ri2OF6 4.֫9EԻ7a4bSL(+mwX_\k[](/U˶-GLW<7;T:Zl Oy >bl,V: )e A&,FqrTi~WEv6~S3RO? Qg#T9&aG7fe=+X^ޯg` 8EAd 2_5 Cd;27[!PHGKn;$\umA[\DȢ7˞_SO iEt* iyo/~k*̞Hw΃6'fMѢEL3~ 7yDJ+" @L(C´|fȟPW\Ʈv;w-Ik ZrJ'U>5|Fyvf0F, SN\3C^؋|Rʹ?pwNlxc!1htYk\K$Ro=;;80k/!\DRtovT?CL$:z߻LGmˈ;]w@ Y<8ȣ0xlmt/$O6ɐu]do CBa[LKQ?3_$S!5.S./;:cП}o/@[" DҊ9VI.,\0c("!/I' n`bGt`^@s~JGPa[,r a*8}Qm8N]_y c&|]M, )īcH8I*7v?x@I= 詷T`g=WĕO׫B2௬[kBخK 93mm*ְoҊ)G&[P7FyThnP8kP_g slA %3p}gc0S<:dK+y! 3:{@7qr*?%XC]7 H߹?R=҉t_Rl0sJ2%+k 2argzDrw%I(㬴!HY/yo5U06ϙ6iԭ|SYb|$*bZA+!Plq/Ejol2!\Vѱ(sRCđ;:ɷT1umQ?gQ`bGn5fSNh̠%$6j%+H\ "IMʿ6jD7 O Ft!I&(&` ˫w@X5{*eR$gKm6(Kn-jqc=g'B+.QL2 /)2溯CeOlA"(Eh94Rr_mUXmEeתR2XDa+m%|H4 ŊGjѕ<ި*SSYPdx;Lq;&T^}͖'v~ҘB82o~{񚞔Cx+"]=蘥yGs[>C+VkoLv1GXD~8IJgqT\C62<)Έ6ȋM4cw_Y&61܅̱^xf6!JQ1Bv9Fp &]dմ 7\퇥R~u]$Яֆ)BT>(f^1023wNƼqe͋xizGM֓Hgz>E^My/EgO+oq-Ukn=%Y{_cB+^OJ wꗲRI[v^ц8D@!~M(e 13y Ulh b[l(= BlyDu&pb&]0%PEպlmqǐTf 3@q=5ߩ`ߣwt6@ŇI>oIO)2nsg)P No^}4`+s.*nQ[moL)moR~&&bWB:[-.*4Jy2jV)͜ISdWPj|U =}a(c> stream xڌp%Z Ǚmضmkc۶5mcbkb'L̛sy﫺R_{) mm"rLFFzFFf22Us'+aȾmm!4pX5ppH;[XLL܌fFF8:pD \̍ri[# G|PQ8hY̍ lrNf@돌FV[#slAkdJo`Ho`OE p5w2(.@c_K!;ˠbkj|,Xm?Bm)Y_βrpLLmdnw) Pwrsh`hob`ne`wq!%s4r0srw4K#_|5DF˵u%َA(%o%?k@'###' 3h1@ ޞvv@osOG O  `452_qnmƏc0O:flkc+fPUmt:f (DJ٘U)`wǃ {~-@Ϳ21}bvm[v9 ͭѷN3 g1 6U;[_,٘Z ݀ƊNFfƿ3+s_/ >phɿMߌb6F 3;񣓘Lh t 6N!u[ ҿ 8 " b%Fb0H!fb0H2#.> %Ώ#Ȯ>!VE}dWC5K\6?a3C>lFVQ- 8|0!mbi_dinggEk,b-rdzggP.Cb>m!'`LJ.7GVf#:'W?"?b=_g>~??w*4YY5 xu۟#WO\qt~BFUp'mi}WVp:-QE/^yvfychTa3׫KVni\{gN| 򵉐}vؗtjQ_K ?Q\!͡LJxEyjm1<.xlT2;`bkaߢL̒{ %Kc.yGm-e3RуlN zwskJ`2Z9pILz70k}Wѷ;EX¢1quW^<{0>́X%Z`{VV~ZcLd3zk7gWEFigݜہJP/goiHdu_?p|K=*Fouz7)n4],fBha^ AȑS8Z.S@"(f\-8.~:)zx߫WlLbJStr1j%E/B-BMauW~0/t0WlH{|<7Z h>j*Ҥ%L^* CetFOT9MA'|2WeN1{;blem^N-O\ֲHMT}hݰ:v1H9J~1^{wWfwTt$aӆAÐk\ uxz}8;OF/vÜ,.3e!2֙Vw~Þ-8Y)ݸ i,q2>R!EBr <1qQc4!u< hbKlvkqRy*\;hVF#PɴKg3xW,mcɫQf7i 3ߨWH`#HpHWeT1e^jCeQ&PMNH)}%|l;|ip Yp `젎 SaΆ2؇ᨭψǵ2%9Mhd/UHuWlIFdzܓP[bk-ǚ,1Ux VoH1M0q#_>.E#8W&藇#?)̈^1{&ye''Wϥ&gdvyϼW e o** NYܝ驞o{*?R۹sT]ޔ_Z9P+B@-3]>8UX p^s>rdu}>.lB"n;ɱ*^&\K=W#n&b/ЂZ{UwB1.' v!Yo?P;3:ohC .86~Xs t:\Y3 *"+r"l&|?|_OqB^WpA`F㖠A-yP x (fOůEcbSF+ԕh.Wbm;>U1)ܩҫ50NDß]! ~O`/2<շOydIAˡܘ\vSʔtDѳ rMUjP+HLTM!)o% ]bç:̲~'! pWfT H&wɉDmaMNH-rL-꼛L;쇯]u@a>Tґm+5ߕ7~Xx)$#}bwM O*Xg@cRP<~:(MWʠo^I&dd% LO,qzILgܸ& c3 eYF1D$ܚDBп!@:{4(B푞 mQ<[= 8Sbwp.4 9ܭhhCa ܡA %;vU?<ʉ1 ־&`}¾%n_ߐu`QtfĥU-.3,L %?Sr\TdG̀wcV{Ѯz&L8dV4tLT]y^|S|$Es"f+g\IJ ${D@y*r2v0jnO/y`] 0GWࡀF:zPtpO 5b9oI^?U%Jrjn`|G|Gxx[,i=DڭȜ26=W1}wm0\¦Vްؗ` 6!҄M'oiU ] )8՛4@2/)Tɿ{˥4\gp60BH2a5fL("]t84vZD_ijiL_A-H:n<߮0\ENX~p>0Mz͋\Mjm&i_[OAj;c3NÝa 7s Czsv!hiQ%l"԰hRϐ?2 c^]_C\QjHr9"3#a³rT(ކϮjg)˫[šeMhB,66ޟj"3!)!9:mY!S bMZ >z,_aB5*c4\n# نHĨwzkQ>H~<0V/8 W 1ƮYv$v!&㝀"Hu?^ڱ :mH51qOpT}eGl>- 9K@k`uW:WyH7eC0X 7n"=qL龁tQwS/DlI/`JPq U*'9{5Oq&Pɇ9f inyCsjצ"Д+H`5ehR/'-yUA NÜҭ&bT=2N @2&a^6&i4|M27Gq`]J\~4*OM:taCk;id(Ӵ);Vis⿥X[GUȞ dJzU$օHnGK.Kk{ڇ˭nqv#A*_D,~5UMR!V1TT Rr*L3Ms.5h򘩄<7K.vjs+J=RT 1c Q-FC\;K7X6}nMnva(>C[_QŜd7 8ﴗK$p-Z;@zs8ԣx$vxkIYa*7Sx 'swKI3Xt<{)I Aj7rkQ!K&j0Ћ'K+aj, f"5| 26~B0W@с䞴+Ws/$Ȉ,y2=sVP&: XvI1;Yfv7@ zkfX[qBh$ 0G27Q؞JBT8'N[1,Q(,`l^>O =؆B9i8E H`܄W`ݍmWQR'ݽ%N31!NNؖ%댱_%hZpsi Rd-︖Ͷ^H36 o|&Bⵖhuv7e/i$&`:.>ZjSqDRmZwFY5~Vnt"2?cQruݕ|R kHs: \O S*P]QoOb|mNeA쐎1"R}} Y{y^M62B/PcLi_iR$uAӘf#vadP*uqu9ieM4CE*C#=S9K6- =Ҝ&!>}/Ma9H:`b-9a vP%S;ON)wxD7At w4nԿӂuDF(`܅+kK l_r_OvStW_fv'`xdH̪~u]0ſfI]W@x*C<UyZZuzM|Qt$2Z[oSI"-BQ. F% &b8\EvAϴ&\~5}0⢌qܞvGf 4/ 83AYTg:l\#=S~lyiz[|ɡߑ6(g)0aOhfQ@s!i5HD{zoh#=q**b]=EClxRJtWtB=8afPc~di Hl؃vPc¾Ke˶i,*_OH@*ORoADݥ]n%v!D:$iCB;$|$iyY7bsKi}+x~ga JQ9qhN` v()(nglh\dV25R&V|9@VeÇV:h2O9\=k`|# U,F .1U"p?V5I4~kP1r8 gfC'T<9tgR5mt̯CxDL_F akʻkZ,=|%7lҬQ>~ Ӆ<'\l};6BI6Q.{=fdz$*]5 }l\8&9]86C8=ڶ10R^ Z@g1qNԳ:]RuRRÂMK{=myUQJOnT=g0Cw.I، ɎX4i&\P,crqX7u hJ76/P l9E*]@}Gs[8D(tb-X `7ʾKa~/ À\Ͻ,ĺEVrtHu ?sO;H`;7!$3FD䯜K3?bf4HZ$?/K2ن<{%DZ/]5{!ٸZP yeAd6:cJ's~OD|N\%!b /vLRͰ Y\fT7uUPa&CEwJ$ʪf.=Y.?$"$,a\{׋HPy|wY֢g1%RN4L5FDY,#)ԘZNF9.v/ 'q60U^Z3DcּZUp!ǧ YFj6G9 )yL$܍"uF=T؂`EpaUksۅ%ke9BM }omh~$JokƓv  _/C_ܾX30ږ$PY&tKf xSl:}<ۄl(Q +g7uEpF2 a_X b"I;0jpVp.k7@k3Ƨ5AG*O)ñ@:,[aRɰSPQ1 |bT7b+BUwtDp]oeҠ/9 mլG+|rg5y*zbWO=}}OIYPpv5/.زqST 5*HJ_K{TX}* (#.'3W=lhƪ gѓ2D=fz98Cj|2>`cGQaR2}.i*1|@:4D7kr}~9ʞ 갢;ϋvuں"6-ؗ_5qQgX|) (U#ΟA@nXt#۵9^!"8˰ʩ\UƘ}ۣP NZvZp6Ś@U,ߍ_Qhdz;81ἵ-GziufBEzé;V.}%˖⮙wH*R) C =A&X"h6}گJ.Ѧv Dn>3N**DƶF/4 B;2o3.dw}\_a~@6WgLOha@,ׂ%S.1[g#EjҕIH$ø+ D˩OCꋑu_f+8i%7 nF"f 1Zji^J"RY87:3`,"A²gqgJVp?^sm%{i3<:ݛytyz̚YGh %MtK4zq D_1 93}!|wz6H_tQt4*,C1T=.0s@ bЦTPG@=.EY waV]$ k/Oԭ檳J)E6EF>CtB"41\Z:/̱M$SfОHhu3^h"lp*@hfc8hc~[ho݌C[f[/P7 `GFfNHCaXQY̲hΰIfmKhk2iwKk2 Ͱ<iKeZK)U(L&͓wk wύuSPrϷ5rO ~Sv>UyBtKT\qX}d)KRuy&V'[U9TxVvzi\ٷtmM6$J0S β⫮C sr[Zm)?eOz, oȯM8V<]e)d5Z=f$A W2fsFpu*"P])Ӻr"Gc>GDCډr-D7SX6 }-WN_*!չ*%"xq̦נ1#k!/&Uh*ga_{|F/ 6 l1ܟI@Fs֡kd{`zLh }7z !~4{4 I_?kH]o '%2pQuOא4 k&0} s y }SQj2E‡&iTHБ=Xnӌut* b4j9sMdN49@jyUx^(PͭBAD3 ľ1G}IgP*Pn27B&ü 1B-/rgxͧAYڶ^-8Ȍ. $发?D4U$PlJe);:*%zצtWOdDUe]iU ?0)SZ+ի(HR x[{2x V+?c5AH6|i+q982 }. AkG1 2:+S,<wd qϪaL ܒ!|)%RP7jW?6F'8DTZZf!) VEzur >eO fvb'5Qϓb֥;_nz,òlJRCQZr\k?I>cNd=}fT+J52{NHTn-i[f7,}2HP1gぁ0SL+M#*IEztˠo낿<\hK^iyA/v]H8l-tZNT D,bָt=0uqn9J%F.jtkAC{QZ]UD7)x/{W`9*4uj= ,s$Ҍ%CnŮzcͤg$>Ѻ%^*wh4L$IqΓD!ϽuId}09ZS :ف?@JѢ@- \$VB_L:Job9Sk*3clS,%X>d _Qi"ʫxWG-d|?ʑ~"킞o1ϒbPJvJ\Y.)n*)o^LN lfB-d˳y3郔VCW^7:ujy9+ggB,(Fqb0!?vAڸIfg55Tsan'G-Eh5dHbd=3qM"rfU^TU3ձdĞE_|9<'̫c(̥s> `; :N ݂* TY9o/ܼkL ڷ~;W\Jbҷ5 ƖƩ[ix17L#JD{F7ĺT,B|!mj,D!풠RNv}X; Xm:dXTn󲟲]֒Vw-Xzecl#Z˖]ƣi#xAй9\$EʨW1V;~J\ eT#";5DuFo xd 栓O*Iq]w9S 8˝rEb Je4Ƚ[6ڋ} pcdsny%!KڥAmi]˽:2Y8]gƄ銬oʜ]ɐ㠕^b1f/o&N(DŽC l'IJ-p=&6v_,TvF4o! 7[qýPnK:=\Y)q&ǀms/ҵ>|Z0ϊc JHK:/^=ӁH_AJ̔`Zcj.e@Y, 6z߆r$[Hb͵5I0pӈᵎ6ԇ''>:>@WiYl-qY9m :95x U:F8ڦPM\q:Ff9"Njp@sOt@ 0:j48 Iڱ veM˻x vZO'9zq\G#wZSLI_*Z8dPH(kMocAJ .{9M[ukz.ivvD ɏw9ϪFoLjϏ%L6#\G6Cg#]-%7҉ G~u1 Cup t7Db3 ظ;W<8UH]DձuhYq`j\$KOW^gp qr`h366Ƅ(پs9pfBT4BOnCF{ MX@UkP M23a_a3*mS(x(SGsW~<#¦TF Mn\R-c:K\t*?;-Fv91NY҃*X`Ҋh-߮:J%5^u+iw!ly-Dj/|K57GQ֡%RiÖ3_uVP8m&|ϨE›+ϗݗ7 9{zG+xh`/i ݎʷBClܡGRg\eN-^Ԙs!m3_>Ow0(k>v";$XX|^ AIYCSxe4/'dr﬙j Go`oݜAc V5dcn.W~~}_.>vx8;X\5S+ڧT |Psd,XNbevE戼i:xo+&ՓQ^m2fH Ќ` bEw!F_'i0bfҳg/* LdIZ3z{#ܡAY(ɖ 6Π$L 5t[P,A Sׁ:A Zuޑp+0Ž\^rYn(Kꐹ-x?lPAdE-,l5 9O5' ݉Fn ?Ows!cNZh#4>`)ɫ5dpz}5ԋeY{'4q!\]Q H|7ʲ:םOA.*IńTWP/ah)0x s}nϕ˭4=j4[ZU;ڤl5 [mm_V@R&gX6P{Ţ'Ȧ:5ԑUoպbv@mߝ!L%yIUyeEg.z@-| 1sO k3a\ϩ#HpR&x 픍уO^5mi>s.셨z !-Y2Ӝ lqm>imBB P>ܹ72 cmoIun^=x`ׁxyK2]K KB 3Mmk'z0=u7"h$~z}c1;.ͺqUcؘ/J.*LHSuO\#% gr3BI,I*|z[!̬<) Ȉ`@0.܅ؗ0i SBŹ;o&6ъ?+( =eĞv2~\/oSEq6%<8&^ƣʙT`9HP%.O֨pqZ}-{ /ESʉq:^[R ͐C/DOn~}vs[6ZFӗrrM[unږ$kb0Ny[ybŦyPkvw{c޽_u b&rSLx鋨`5+}};.+Dg*si~ 8KHi{kwaAJ^.)7iy11b>ׁV? wLht4L˸%Tib"?-H(&\SN L XߦO-emrݙdmhrЛag?ֹCTڪpanBj8em^/.JצvΓ1;xi?L;ğ_$-1кL EfDQVs<'DKO]<7r\vP/vpJo\x8GP02m|ݧїPKXqx߆J>zig):WDӷ >gٻ̠FDp!Mi2;l`|M)٪ }V$J(v%Jqm0U7XD#;MjfS9R1 mPt#+'mA#sLp i_lk/2H|ěHV韶 dSC@l67T;}TVZpr8x;/o"Ku>` I+)vԔ%2'RNͣ|sjϗl5$QTW n.wf2rxӳVe%2);?dt=-S$0DlYj~ځ̧y-i' j=R;:Ax$+p̤0Hu8Wur˗Y}t8(S)@>BHzflV]s=kAftPiǼ,z!@ p/S>3wT6Ƽ4I|[[tt!>>3ׅuJ ]'+P%Ƚ" k_UNh #rrp 23ey7/z endstream endobj 378 0 obj << /Length1 2518 /Length2 11409 /Length3 0 /Length 12852 /Filter /FlateDecode >> stream xڍTk6Lt] 0Cw#] "-HtHwI xy5k1s@!cdRtqpr4|ܼ `7?b g W( "+`vN#(#$ C'WQ<l:A@P 9'goW~-Y<""BdA`K t9-='K0?!mܜE<==9PN'WIv' \=@V4+`ۂ< LA0w#詨A6`'Ͽ hi x!6kB~N09( <+ ;.wX VrN w~`W%\=CaeT10e6 77707yYr K[ ` +0|@`tXl01o ;|W6{<ߟ=UTW4auN^_//@_ ("om $=U Ns5Y ci:f`~q3nnKAoGNH/5_v6np-^Y kU܀58F0TY5 ~v)n핥=ހ/6eTX:Y/^A #^/l@^M0sX;b>PAoH%\rH%D\ "!n#p)="^#p<"#?"#k>"ֿHƮ`:Ʈ`z`<"#="Hf |D@%l vtw|"X<"XV@K{j(en `!kG9߿_,6I#-qt|LqYaA.ܭ]`Y=[Kf]el a~aU%]ၕY zX}Ns=*!9~=>3HT/_o caR,̮&Jܓcs,ǶN9_~̼L痭U}OFD/Lj-Ե"[[lMICy::L^Alurqo3%`#o'U0Dg%ҩJ1Ci4=fknl(:p%>X(Eo2TM3˲.1-4)8|PqzbDx!yQ7G6yiEʊJ`MpӜ}kfDyHukN)<0J3ECMcN$A5c$8`4,҆cwݸleKCt/%ޙt!l)[8 /݂;w{ ITI,cj|մ&!YhZ0ߵ*'Ȓ x5U7ptS!DZ JfCa^Uk OE26љJl[ׇk9цe %?rӿ(}rosj|U\2Hg ؖ"Ԏh~KM7w*ՙͼП<;b|9a_JծE (8=`A3Žs:k/jnr$s󤓃֊IWX*8"~6ׅ}mx[}C0rkA{^J.qTⰜq5*}A" 3MWtgcRAh} y!IVAQe#ic1ߪ+>q Xt~U tJmnECcbQn_KTm-(zQhˋYkr=+e:Ip(˓İfClQm5u?^Glz Dk4hAIcZujxFěμӝΉ~$!vwDgר^ 4+hV6zT2w[CR/Pu2:e􌥮{wF8d܁>=G6y+?HBPNnƶӍ*/rIj:rGKC)zŠSm98RpUjwv.3r4$>D\ˍu|Eى U?]3>J hr$x# %wD#v` ^"~H\QiGs8!+Kd5zvmݞZ_ >I(.T0Lcڴ$gQpNIxk*%&ϥŒ"_vޟlo;G<஺93STNE1h;7Y9'\Q:Kࠨ,A;Ac-lrK3\GoA~"c|%|ɩd,5lҳ``L40ayi˞[Qcx9iΗ裘~~'>SBֱAG\m ^vzwc_*-71:&=)tZI!!yK֫l6c{mq;fAttw̵-ϔ̃q+UtVm͘}/n=?P-پm-+쫗K_\[y#b*G9痉g}C5\G"LoFXG}۸3{#|Vƒ+8UoU|ތ'噟pƸUW{{TLmfL #~٧s7+J7{.ܨtƏyu{c&j^d%VsF y2z4MZjn F<$(DИ&M쥇ג5 q,g8v:@P?FE39zEGBs\):磻t_!>au2z{v*0_Wm.*){@SD I>S"tQ[&s}$PBBMmlQ۸1j̸..Nnbx38¦ԆGY)Fs ?4=q W59.HI̔TjUO'M@bC [d˞lO,tM:?}sa ע1cb_-$S G+ J*w'GRhwx6㷝'ԥFb*B aWWJ/Ifp~u;}l65CB%b;NZ=zD=sgKh\F=EɌȪPq_=踦<Dz6F6HkcY2f]3U]D}}ji ,\'g|N`ԚFKy:b:΅@1fyOGuG 55;(o%'yɾgkq<^%8cdLcnp_~ye);df"O0vK֍PrfxvT{[#s<${1CeLˏ G,uL< bߒ}.GЛgC3e; i&іfneP|`؉t_`f׽{=R}OEzZ31p!IץQNGlj#eDdL_/ѡE~A݇?xQ-wbdWd.Zx;mDhr?4/*}\qᨰK"$[hzIa"PX'`-2ϣt@3_{"WaM(Se Od*MIIDr]]NEʆx0ϊ&w}O1w/X= A)sdo-p6<jVeFӂs\@9 үEuE"+=: & 35 #)$xkR1O_?{i*nk+¶GRgHlQF|MaR .0 ݈&0yfxI}Lu (3ӆ\Җ"Hd,"ao¥U+$>ZzS] U3a@m[!Ѵ!~֧i"Bs7Za_9 QfxE`ӹxכYp_zށ" ),m)`a3.l2t}!@ah&ι4}8qE`vm/ΜI4KZ"!i>KL!e&jo$Cs61J2I8Ls[ A@{@!Mmœx6Gx){iiQ=>%b0U:='B- ,`ːpòBlƬZhݐ7Ȕ+oPPJ8l~r4ë X(XAa} ZH&_o;yI2}%u1 i5Ӑ=e6=cl ׭hvOMݧj/ NRt7p93 Fk'bxkٮp8}S[,M·:=hޭ߫ S?=(dVsfFWs#/9OuJh";1s/K=UX4b}en#e~A=_qR$0Aǎ6gߠ>T; ;57]kosI~Vmk:1L g ntSFkA*ɑckڲlmGk%M,ܯ9hY2+_*xk2w(`f!>-ܑB#J87Tyo"5FC'0}xUT^ϳbwF@¼L5ӯ{on~P&$mp`Ҧ&~q7Ya}GZrd&z?ItƔ~%UP8.T95Wa[n G+n!Mh"e;4_}zN aլyOWYp"%G}'IQ8` D9053Fv_$ԛߚ!v4Gy^]p-5 XrTbof(hF,G Rr@Z%=CFj!#c/13_/,kvɪzL &S=*l}f{ֺы:l$?jg{)\i1 M:܅C{,ur1zc!>w9 w7\~o[5ų5J |t Ǿ11YA{; =eS>Co˵9o3 M§f3zPnN5)S;qW.W⛵m幌B; 5ӊ|7o&/?NvyV*7+VIeaX,d©Zv$V:k_nM">MdOIn2ĈUS@2,W/!42@P J\./؇C\^32W5)'G%N4>ɞx`s1:ZX(kXI־dHQOY>T,}fG=&Yo[RaQa xk3k/}v4eSC S0_) Eh>ȫ^MRV2L1eૻoK<1jb.AEV;8L!|ӥDȍpÊUYUXhGABH^fj%Km#벊تd})U'Rs&+%IϿKy&x5P2K %Z ! 2b{XVQ M iU!Ƴ`;V.HɀCz]^P0Npt[qll3I@up|j!T&~;nUkÎ6Q/Ye/Q8niGu2q ;Ϧs7{ sO5f> dӣr!!D .%p2@?tQZW>jaH&kKx)Ca^=Rɵ A1Bedgu1"Oij 5;0h/ %z]mq(]yE >XP BlJ(K ]_Ǵ|?yrЌp˧H|KWRӱJ#Ulb5O\?%2fa=u6аh߹Aǚ' OLpg u b4myqG=mz*6Zi/!D,|Q^$V\}9? *^$U+vS%BzYe9~Gdm*b{׷-wnPeĚ!D 9P #}̞1uQhy eo}M@4k <~qu g=֡y~5};W "YaL Tal֊}Pxw-יe>14 3˛ qD>iMɌoje&";1fHC(w(pۇˠV$PmNQ`.hk cݱQ}BubƮhRw>_|{U]B6 g 72E#0xPifc=+$/IY.Ez5cn}Bbx'L<: ! !|CYM5Z Y?!b 8fB&u^1ʁki3e*>R*E:tp B|Չ -6s``ä+ݘH/.йzN~u ZS`qeƙ8m=;=Qث_u7 Ooi5i~1NVI:hBLhܧ2E`NY?biabU]3]!`yQ,% 6B ;NGQ&LV,~ͦ}4w^ʡ0cM9< "3,b(l΄L\>О !*.pHub._i7anʹq'}aS+.<%=Ɩ^oZHΐ{9˂# _D@0aZwҙ C"K#7gS!}lIF|vג+)?&$/L1Iω[3Td"rB`C?aTcrڈpbLP2 g_߷AoʅL[CB_xU~ ToKSDzӡ9sw2ؼ *K|4.|/uJ%c&V9!E\w;d"]00\)tvz Kd"QXjJ +=Lh',@٬]|JR({n#dVf4' Xb2nur;9F6½hֵł47`13#z"֎6r"wI٦& 6,v"zE㖉3WBjC!uRC=b7EG,>PU΀}ߵWy\蟕sD9q$c Y 5&i51w28U]U<.j){>~sh{0aAk>5N(LitX8yX%wʝke9)&+q$cBtKH=yf-YzXe2ץ7ܵb^T|-F5y=ށ`IAcDRDR*;gmzf<\ө rRorP< ᫺HܣvXׄͬ> B9j}a'3Hd#x߰m#tRG|q 5 h><3^!;oE60E*9k^[:Ǥ+F>/_L-0~>+b$Ayob̠~po;pēX^YV~qXK\ (OԾmLZnN#M^$WuäI>>ӌ5W)kdBxXOnR4 DZn9]&L JgZY{KfU'o2RNC#no=GI(vSE=7Buv}El {Zi[^}">҄yf#8wi ÐPߝ@7/D%˽057 vSIXGn_h.ǓuD( J3{[A(>Eau,k؉1ǗOmy D͏&HuT 23j$\`V@n0M];Hza|mJXk|\fhG+(4}Gv J3~/vv"};`!_WFb7Ro'ËQҎ}^f+[M ^[r]E烢medوHaB'}g?1I5|(1 EF#ڻ&RYX,{Tjݵ$ޠp`90n`MHfWyfZ|6LW0%Gw endstream endobj 380 0 obj << /Length1 2035 /Length2 11829 /Length3 0 /Length 13083 /Filter /FlateDecode >> stream xڍP\[. ];g] .%K;9UTOӽZצ QT258330DTULL LL,H '+H@G Ͽ"@C'L ̓H;[Y<̜^TfnnNB@ @h > bk : *>s'';FFWWWCkG[3j:+ t:MZΌjr[bkjV c#P(m&M6f#_Ɔƶv6 3) Peprsژ&Z9ڂ ] AVF`_ąɑd;EnU189"O4ݝZغxlLL'alǨfwJ!LLL\=fl۽/_bpޞvvSp@o)h898=o 0;f ?b|@ <{߿tebkcWETdi@ `gcppsۉ!?A01p+HOՠ/y[TF;1<o/;[YKQZCxlK`WVhrZ)'C٘YSF8 hr26k0^1+ PN331/x-#xRk'ۚ/v;xXE45F[' 7wC9BE#? (u'QfA\F7i# G?[:Z:rAKhdfbkee/3'<^sL`rc[mo``ko bf|}aeApbgeഭ@f0_\W_jpv"ف ok/8 lw>` ?-{KhlXfn$A{\g;;8o&Z3my,j鉶\Pi$S{.9t8?'PWl8 % YL>5^Y?Vyj iqD~B^-[ )흹s]$KW>)Wq ?NQ_4Kc1K DO@y>{s;5J,K}S{%ac\űOslS0Qg0r!bѦ'BD^bh  Oi&y–Ui|FƘL֏&lXڮۃZ 3ЧL |^a,j_LNCB X=2ݥIv$Vxh7rij̞\6Z<[\(b@1b4H7#& mjy3dɺ\$I*ތIEmʎHnٸPRyg3Wn;tG I H-#f>C(O%O_! ;GϨ˙ʒ*B%^roCcO/KeK:!T,?Fuȸgr]"ƚG^]x+=O>I~MQFu'(u׫~w1 !7yȍ"K3q0m&3}Yhq]vC5ܢ9y;gxȸ&׍gҌ7U/O;Q{q=e$+YOcWk;(;t1?X0hrqͣ0g5=\ )1 )I mg0\'Z5 o,d\E|W֛u'QbCpo ZBCisP2G+pU9o.FK["bQ>var6-{ק᭕f@,bFšW>wc C8"8T sͅoP< 5ixata<~Ӽ>K/b p|wW6(Lӟ8=WDRˍ)P|]t%Rدh=)FI l| O+8/ߡNT#NV;2ϴ-7%%` 9j EBXn.JVrf۞8pOw528neͿ_G0F`:Ҍ)&͂tE;|HGXxi@I7F8Â?j5|qk9 m3PK' V2cA+ˤhJ\kM:э$TTⰮ&%Z©'~2;`{NQynQ`>@ڎ-.[d(̯ltl܏n/^D}l>lgҜ|ieoEj'!{={2̾a@:пK>tޅ&zyj?M6,e;U-CbYjz~ޢ&D# /X%6>@ϸ%ʴ%\if~G=IDC<[/,]JRU0_Jď3lHU)AWO 7WtGCJxD# ?k,KL.u?K^e#OY:,1,YWL7 ?_ᵌ$:l|kύ;ninXQS&( nv09%*K?fvړg0 2Oxq4ˠH~hҾ!]S\zV'&qxas_w^BJ"O› Lzf :LRF1l}~dbUb\|Ug9^O1kJJjpm~~$,!7o"eHY~@v2bn+T6`X$_7is)6:olwŐ[] 1eLo|_|ddIwW8:x My50eZ!XNnUUfK2hƣ!j9*ZK>T?QW?Xq`Yk]> N$I\y`x2Q?e{݃zs1qস< ^=ʧhRG46k]AE1!=ErXd[Ā,YF-N@ػi `^t$;j2]>؋*|yE,\g |1dxE"a%G.T~$+1G:{0Q*@ژzVXM,ٌÇus}\o+պmkZįtXJr$Ժo)#d:w;sr53$ef1b@)l6V0 vȜ74SBƆyWjkm*ӧJ6[ kuj=zЇ,E^0`+`˧‰lynZ U;+ SJr#AńY"ކzr⪋qa^M1:,+=u᳟gj(*9Ϳ)9`$Ջg{X|zӜBj$(fq/E>2 /MWN@hTZC.'j x(ժAb 04ΧM]!VC|/P>LWI b:&AX/z^ p:ev!6]^6461U섓VaWp6y817'%.W$vNRD"^ LBv ggszy}7k*c:bۧrșv8!F0v/fĜN:aS9| C"gQ \ ˞] BёC5͛_tr UEN6ɍ)PސҩO(=U8ˆsh.O.~{pƨ'KC^NG`"s܀R jpY9a#;ƤCSF[vB|Pq+&]ئk" $]X]C7||r@#=u<{n>J+lhc3#q7&э ZoAj*8P7=UC0~uvuE^+nc^=һvF1*vs8F{!qd}h ƕDq&J' ?r+HLo!](p9ɋr$T'{GsEw8{" j5k0EB|ЅAA)KxW+R&j:55F(eVs p]M 2~41ζm9&LwnӋrGj/^7nuIqw# T{d<3 &>/|S& & r NYN``@nq}₌;* 19ԽHd:ϕ$1rw5ExB޶kp*:sfc:fzLHRpl_tj ]ꛫK3ݪ7a19r;^Q9yȤ嗺~xXǸlBFct[Jn>e]`s85B/x΋ϓt}rhhw{j\D4J~A0\e{^ʵdV1{|2 LEV" +pc{nM<B2jaS(8/|gX:8E?{~f$<(lJW^X"̢ɛ!~.g /s]96(E}~/*pqՉ%!.m8pRJjX2X6nl Ԥ笎K;qmCGflxϽfKhR?OwL=< gD[Foun? f3SDF$2> z[c7cj!9,A70 e4=+8ipm-d]a㸶>(OD})xHϩYfD|!aƭC\B%f遘t=kSci[ֽ5 Q&Q@ (piAoߺXm,"}61;o5OpN4 Ƨ>SDJF "ŗ+DnƜ`8d+~|+lTA0f1L*_# .M̨+5zSf\iHI8gU_ЇI% f {%1bsū:7u]tVZó>}7.GUlT #i&00>V^$oOȓ*piFyZHzﶹqqlVZ Ø3MuF +{ʢ'?Ŕuُw?'kH|4d^L\>KўFcbO@Z=*”)]hv)jbO߹j[Ù7q\ӈNY4_Pzr42]THd.j`u:Ti2H] 瑌iiiPv| a$.tawmt_뱱B|PeK!3h煹{uL/9Ȯ`k;͋ =?Lk HI $[8 qzWA8 X&?^JM]?[/"02ܾrJ+/}؊~P"c"dZަȹm.ӾFK563(G0naa LށDgmzk IW,ɊZZn  t ?Y0ԪH~ %\!d<>D ~~'慄g:F*oL2y)'+PFZi, ۡ9mX+@I\9*';uǝiz3L3|Mǖ]rƜGȗWٯGe,kmJXw)kn\|z4&n1e5{G%9GRm lq;nGn=!Zz0rco#8{i6"@a ,$Aڐo"3 6y&>B Wm4k %Ʉd-$a#U"F1|({GFjPdNDd $v_ܸ,?~h=XW6]$lI.U2.fɬ`i/{AxY@'*Ͼg!,T$vwZz!5eMi -b ³IF 0.7fQCWe΢ϗf,h|#UM$E! =ԙ=8+IWTǁNJ:c#NflʎO9ʚ-?)iURa ۮrekkmx N[[{O{z[Yk]y~BSL1u9f[JCXBx]#n:v1SڂU׆P?_kch^y6~Zcdx:+-z> :o“/<ն-2\RL GrN_>& _[pzڹy/(;nLeB8FG \t~pTS\_c%.n=΢B  _"0V-or2m~ձBA(I!h_yEBy#pQ3`=A9 =Nke#cŊh6:Q@_mBwb@nx<'!/L;1[BuD>|B|>&0E Kx06x;ȖYM* pCY~@)'!$,󢈌 g6$Xd9PW3>Si*Kv9<4e//UJ쵬Twq|th+8t(z jHM/a~h.KV1tNMq_WX-{aKC~Ԧך4,y#wo- nAf31EWW[˽f  (Oشӫc) 1wO/jJ<3}%$@G%=IZ%.VYaG챷}VYM9=jE"a7 oz89;Fh-!]|NGb_L7fu/KWMk,BGLbX%ޢJ;e]XC2rW+!_^z.K7"INŊ@Jqd,RvhPw/ˣ]8S,5:c>jhgOuG9`Eΐ8w)ouP PYY{mF{|z BN-34Mig.`UeHG ]H0[78e {,TaffL }eo즱v]3'tEýkЃ~E*:()yrFw-G7:_W9n!')RtOҼ|)+ P-ɽ軇m>pgx=n9`G%-{nߦIkծye0Ɋ„v;yW\ÿjg϶2*G^\G>`^d5UúOr`p(,sG&>.R-p7gw5̅?ZS_eXx[vٮ  E,KHt;YoE_/h$'ђn43 Zhur>_YV,/u GrJGoؤ9Ani<@<'۽<?穧8 5sE={ǕvO.l-D'9Iߝ &*lqg;̥`v_ p&awrg#.ͤaHUcc%ތA0 eRzKdejy8-FZוSQ{mL7CBZ sn͇CILM",v9--i*IdaL\< 'Ui>guX1>-lK9E܌HBE͌p͡}fdkRwԶC9V0C_ OBT{lIxkp)O'aIo'3 ޽q}/=Mhh4>Fτ#ZkXM~@ו G3.M6?|7ךߏO\Fԡ҇+,W-&ʀ'4s9T]wKZsZ`OןA&P+P]'[KTy?sp9e.ˤKP 9z@*-wѷU87G~#Oe6e?[X[D[ȯᑪB˳> ;Gm~,QyhH.ձY{Afw߾r3Ē߇uHO?|ֈCZ`-qy,Ihh) zH 2"Ewy26EL \(ѡ~x17͐B~[y5q3a"b>|8l%1dI뙵4ދwA7ƈMueJhs%n~${lwPZdr.p>2OKe,<U@m/j ${ ;UϮy<> endobj 345 0 obj << /Type /ObjStm /N 67 /First 590 /Length 3539 /Filter /FlateDecode >> stream x[[s9~ϯ#Q.QT%@ 70ܖ88͐{$rmp`vp[-˧sS#$Ko·%IviVa#L:ؠ8a@D#$\)3pgeaqf'@fDr3ZP4C%GnG/e@5aQ @?PXj\rD[dh1"BL1 c&6zLLsb[KT+f,JfX51@hX c8"`b`c,ǵဝC.b0 8PhęAY:`JYdgqĐd58a9X2gPp`%da'WǁL1gy28Cp@G<38829= T{h:~7Os85B£@bXոwY4#^w}Tq}~/2|r>Z?dD눁iUr278+.\,v!>*3IF9Z@.p(XՊ{z:H0FC=P\s v%Qe(27JNf4"63Y\gzx~!m )l֨Ԉxx:8ClO~CFª= vUIkd RٌWKZ{ v?Q,Z@3< #21`tK Y/20z񡕤 >' b t>\N_$.a',fibBC:hP},c:97rUxh Cۣ,F2NN.)_G?|IVcqx.|ҥ@G0LwLX03XR,8#_k/;YL}zˀ2=0~!nj5űbH=O-xdܶJ&0 I7> #pC8T9-軽#`MWVa^ _5pǑ>Pa< CMv OHtL܇//7GKF*oJz/oF *Gܹts pw ;TaM@J'Ƌn'W.!=PW݈?P909(B3\FAC4x8Z*F? |ҾΧ;[[t늴CDjf < n@`%B> cg\P0k~f5IH-Rc}+qvfq )a]*CJ?^PP'&Y=_M EݫI34ӣg~ze05b||4yı(1-}ڡ{u[9tk;i>yQxEX{pɇeoo4_6VMP%M^]ݣOszD =^+kڣ}ZP~˯oe"[ gKa?.]o/xKV-} $VooϷWN.'*j-vDgky>(j[)Vxᯜ|,V6>L|*f|&><哭3m&l^e3Gy)l sz&~߂=8]6y<$1bA'ûL+bwZ}o2ܩH \ 5z\ޖMKzS[Qgn;eC:ci , ,GMYm6Y;y| ,8&2;3ĴP2ٴޮ!RHeMMddS.eW6K.=;z)r0IMʛi= fXhYՍ*ӳ5:Vo<>\)d2 v yJKLL'Tp*"2pˇH[̢MW)d2Fge5 )xn4ֈof<|.ӪfB,Mq VP}k T9 c q3 3[x&3&0x[͙9QV"[aFdЩ`KJb1^#pN^#n2oU2Lo!M5kt+[ϋ)G>CQS^SQ[]7y9QFuqz4APbUX<; _EtS~ Oܜu:͊wZOXC/6-)D-͡e*-"E\Lsm=_J/bW-bmm*+q*&Bǃ-n]M:" ni;섮~q15In޳YM],I1򉘷rZ endstream endobj 401 0 obj << /Type /XRef /Index [0 402] /Size 402 /W [1 3 1] /Root 399 0 R /Info 400 0 R /ID [ ] /Length 902 /Filter /FlateDecode >> stream xշoQ7뜳9l?s9HPQ@KH @C AAh(|4nos_\M~tT:Q DZ/U$k=TI ˬuS@*Eq4N>f@&ku1 PP(Jq.n Ϩ*(JhjZzh6dnnEvF6stu*hNgj3*/. MVK1aM %JJ(P"D@%J:M+asJϕ+= D@}*A:d@&7o9s! !%P  K+-yy[ڠ:zyAaQM0 3`#cIܳ'#faaa 랷3ll8838 +w~sp*rA re'rA !Kܛu [ϯV刔_X+2˪`/@C 44@Cf&'irpY|s endstream endobj startxref 202507 %%EOF malt-0.5.2/tex/manual/manual.tex000066400000000000000000001107561400455127600165450ustar00rootroot00000000000000%\listfiles \documentclass[11pt]{article} \usepackage{fullpage} \usepackage{amssymb} \usepackage{graphics, graphicx} \usepackage{fancyhdr} \usepackage{subfigure} \usepackage{ifthen} \usepackage{version} \usepackage{tocbibind} \usepackage{makeidx} \usepackage{xspace} \usepackage{placeins} %\usepackage{times} \usepackage{booktabs} \usepackage[colorlinks=true, pdfstartview=FitV, linkcolor=blue, citecolor=blue, urlcolor=blue]{hyperref} \raggedbottom \sloppy \parindent=0pt \parskip=5pt \newcommand\MALT{{\sf MALT}\xspace} %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% \input versioninfo.tex %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% \title{\bf User Manual for \MALT V\VERSION} %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% \author{Daniel H.~Huson} %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% \makeindex \input definitions.tex %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% \begin{document} %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% \maketitle %\hfil\includegraphics[height=4cm]{about.pdf}\hfil {\small \setcounter{tocdepth}{1} \tableofcontents } \newpage \ibf{License}: Copyright (C) 2019, Daniel H. Huson This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see \url{http://www.gnu.org/licenses}. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% \mysection{Introduction} %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% \MALT, an acronym for \iit{MEGAN alignment tool}, is a sequence alignment and analysis tool designed for processing high-throughput sequencing data, especially in the context of metagenomics. It is an extension of MEGAN6, the \iit{MEGenome Analyzer} and is designed to provide the input for MEGAN6, but can also be used independently of MEGAN6. The core of the program is a sequence alignment engine that aligns DNA or protein sequences to a {DNA or} protein reference database in either {BLASTN (DNA queries and DNA references),} BLASTX (DNA queries and protein references) or BLASTP (protein queries and protein references) mode. The engine uses a banded-alignment algorithm with affine gap scores and BLOSUM substitution matrices (in the case of protein alignments). The program can compute both local alignments (Smith-Waterman) or semi-global alignments (in which reads are aligned end-to-end into reference sequences), the latter being more appropriate for aligning metagenomic reads to references. By default, \MALT produces a MEGAN ``RMA6'' file that contains taxonomic and functional classifications of the reads that can be opened in MEGAN6. The taxonomic analysis use the naive LCA algorithm (introduced in \cite{MEGAN2011}). Used as an alignment tool, \MALT can produce alignments in BLAST text format, BLAST-tab format or SAM format (both for DNA and protein alignments). In addition, the program can be used as a filter to obtain all reads that have a significant alignment, or do not have a significant alignment, to the given reference database. { \MALT can also be used to compute a taxonomic analysis of 16S sequences. Here the ability to compute a semi-global alignment rather than a local alignment is crucial. When provided with a listing of gene locations and annotations for a given database of DNA sequences, \MALT is able to predict genes based on BLASTN-style alignments. } \MALT actually consists of two programs, \program{malt-build} and \program{malt-run}. The \program{malt-build} program is first used to build an index for the given reference database. It can index arbitrary large databases, provided the used computer has enough memory. For maximum speed, the program uses a hash-table and thus require a large memory machine. The \program{malt-run} program is then used to perform alignments and analyses. \MALT does not use a new approach, but is rather a new carefully crafted implementation of existing approaches. The program uses spaced seeds rather than consecutive seeds \cite{Burkhardt01,Ma02}. It uses a hash table to store seed matches, see, for example, \cite{SSAHA}. It uses a reduced alphabet to determine potential matches between protein sequences \cite{Murphy2000,RapSearch2}. Finally, it uses a banded alignment algorithm \cite{ChaoPM92} that can compute both local and semi global alignments. Both programs make heavy use of parallelization and require a lot of memory. The ideal \pconcept{hardware requirements} are a linux server with 64 cores and 512 GB of memory. \MALT performs alignment and analysis of high-throughput sequencing data in a high-throughput manner. Here are some examples: \begin{enumerate} \item Using the RefSeq microbial protein database (version 50, containing $10$ million protein sequences with a total length of $3.2$ billion amino acids), a BLASTX-style analysis of taxonomic and functional content of a collection of 11 million Illumina reads takes about $900$ wall-clock seconds (using 64 cores). The program found about $4.5$ million significant alignments covering about $15$\% of the total reads. { \item Using the Genbank DNA database (microbes and viruses, downloaded early 2013, containing about 2.3 million DNA sequences with a total length of 11 billion nucleotides), a BLASTN-style analysis of one million reads takes about $70$ wall-clock seconds. The program finds about two million significant alignments covering one quarter of the total reads. \item Using the Silva database (\itt{SSURef\_NR99\_115\_tax\_silva.fasta}, containing $479,726$ DNA sequences with a total length of $690$ million nucleotides), the semi-global alignment of $5000$ 16S reads takes about 100 seconds (using 64 cores), producing about $100,000$ significant alignments. } \end{enumerate} This document provides both an introduction and a reference manual for \MALT. \pagebreak %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% \mysection{Getting Started} %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% This section describes how to get started. Download the program from \url{http://www-ab.informatik.uni-tuebingen.de/software/malt}, see Section~\ref{sec:Obtaining and Installing the Program} for details. First, use \program{malt-build} to build an index for \MALT. For example, to build an index for all viral proteins in RefSeq, download the following file: \url{ftp://ftp.ncbi.nlm.nih.gov/refseq//release/viral/viral.1.protein.faa.gz} Put this file in a single directory called {\tt references}, say. There is no need to unzip the file because \MALT is able to read zipped files. Also, in general, when using more than one file of reference sequences, there is no need to concatenate the files into one file, as \MALT can process multiple files. The program \program{malt-build} will be used to build an index for viral reference sequences. We will write the index directory to a directory called {\tt index}. In the parent directory of the {\tt references} directory, run \program{malt-build} as follows: {\footnotesize \begin{verbatim} set MALT= malt-build -i references/*.* -d index -g2t $MALT/data/gi_taxid_prot-2014Jan04.bin \ -tre $MALT/data/ncbi.tre.gz -map$MALT/data/ncbi.tre.gz -L megan5-license.txt \end{verbatim} } The input files are specified using {\tt -i}, the index is specified using {\tt -d}. The option {\tt -g2t} is used to specify a GI to taxon-id mapping which will be used to identify the taxa associated with the reference sequences. A mapping file is supplied in the data directory of \MALT. The options {\tt -tre} and {\tt -map} are used to access the NCBI taxonomy, which is needed to perform a taxonomic analysis of the reads as they are aligned. Use {\tt -L} to explicitly provide a MEGAN5 license file to the program, if you have not previously used a licensed version of MEGAN5. Then, use \program{malt-run} to analyze a file of DNA reads. Assume that the DNA reads are contained in two files, {\tt reads1.fna} and {\tt reads2.fna}. Call the program as follows: {\footnotesize \begin{verbatim} malt-run -i reads1.fna reads2.fna -d index -m BlastX -o . -L megan5-license.txt \end{verbatim} } If either of the two programs abort due to lack insufficient memory, then please edit the files {\tt malt-build-gui.vmoptions} and/or {\tt malt-run-gui.vmoptions} to allocate more memory to the programs; By default, for testing purposes, the memory reserved for the programs is set to $64GB$. For comparison against the NCBI-NR database, for example, you will need about $300GB$. All input files are specified using {\tt -i}. The index to use is specified using {\tt -d}. The option {\tt -m} defines the alignment mode of the program, in this case {\tt BlastX}. Use {\tt -at} to specify the alignment type.The option {\tt -om} is used to specify the output directory for matches. Here we specify the current directory ({\tt .}). The option {\tt --tax} requests that a taxonomic analysis of the reads be performed and {\tt -om .} requests that the resulting MEGAN file be written to the current directory. The file option {\tt -t} specifies the maximum number of threads. By default, \MALT uses memory mapping to access its index files. If you intend to align a large number of files in a single run of \MALT, then it may be more efficient to have the program preload the complete index. To achieve this, use the command-line option \itt{-mem false}. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% \mysection{Obtaining and Installing the Program} %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% \MALT is written in Java and requires a 64-bit Java runtime environment version 7 or latter, freely available from \url{http://www.java.org}. The Windows and MacOS X installers contain a suitable Java runtime environment that will be used if a suitable Java runtime environment cannot be found on the computer. \MALT is currently in ``open alpha testing'' and is available from: \url{http://www-ab.informatik.uni-tuebingen.de/software/malt}. There are three different installers that target major operating systems: \begin{itemize} \item \itt{MALT\_windows-x64\_\VERSION.exe} provides an installer for \irm{Windows}. \item \itt{MALT\_macos\_\VERSION.dmg} provides an installer for \irm{MacOS X}. \item \itt{MALT\_unix\_\VERSION.sh} provides an installer for \irm{Linux} and \irm{Unix}. \end{itemize} Download the installer that is appropriate for your computer. Please note that the \irm{memory requirement} of \MALT grows dramatically with the size of the reference database that you wish to employ. For example, to align sequences against the NR database requires that you have 512GB of main memory. Double-click on the downloaded installer program to start the interactive installation dialog. Alternatively, under Linux, change into the directory containing the installer and type {\tt ./\itt{MALT\_unix\_\VERSION.sh}} This will launch the \MALT installer in GUI mode. To install the program in non-gui console mode, type {\tt ./\itt{MALT\_unix\_\VERSION.sh} -c} Finally, when updating the installation under Linux, one can perform a completely \irm{non-interactive installation} like this (quiet mode): {\tt ./\itt{MALT\_unix\_\VERSION.sh} -q} The installation dialog will ask how much memory the program may use. Please set this variable carefully. If the amount needs to be changed after installation, then this can be done by editing the files ending on \itt{vmoptions} in the installation directory. Two copies of each of the program \program{malt-build} and \program{malt-run} will be installed. The two copies named \itt{malt-build} and \itt{malt-run} are intended in non-interactive, commandline use. The two copies named \itt{malt-build-gui} and \itt{malt-run-gui} provide a very simple GUI interface. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% \mysection{The MALT index builder} %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% The first step in a \MALT analysis is to build an index for the given reference database. This is done using a program called \pprogram{malt-build}. In summary, \program{malt-build} takes a reference sequence database (represented by one or more FastA files, possibly in \itt{gzip} format) as input and produces an index that then can subsequently be used by the main analysis program \program{malt-run} as input. If \MALT is to be used as an taxonomic and/or functional analysis tool as well as an alignment tool, then in addition, \program{malt-build} must be provided with a number of mapping files that are used to map reference sequences to taxonomic or functional classes{, or to locate genes in DNA reference sequences}. The \program{malt-build} program is controlled by command-line options, as summarized in Figure~\ref{fig:malt-build-usage}. There are three options for determining input and output: \begin{itemize} \setlength{\itemindent}{30pt} \item [\itt{--input}] Use to specify all files that contains reference sequences. The files must be in FastA format and may be {\em gzipped} (in which case they must end on \itt{.gz}.) { \item[\itt{--sequenceType}] Use to specify whether the reference sequences are \itt{DNA} or \itt{Protein} sequences. (For \itt{RNA} sequences, use the DNA setting). } \item[\itt{--index}] Use to specify the name of the index directory. If the directory does not already exist then it will be created. If it already exists, then any previous index files will be overwritten. \end{itemize} There are two performance-related options: \begin{itemize} \setlength{\itemindent}{30pt} \item[\itt{--threads}] Use to set the number of threads to use in parallel computations. Default is 8. Set this to the number of available cores. \item[\itt{--step}] Use to set step size used to advance seed, values greater than 1 reduce index size and sensitivity. Default value: 1. \end{itemize} The most important performance-related option is the maximum amount of memory that \program{malt-build} is allowed to use. This cannot be set from within the program but rather is set during installation of the software. \MALT uses a seed-and-extend approach based on ``spaced seeds'' \cite{Burkhardt01,Ma02}. The following options control this: \begin{itemize} \setlength{\itemindent}{30pt} \item[\itt{--shapes}] {Use this to specify the seed shapes used. For DNA sequences, the \irm{default seed shape} is: {\tt 111110111011110110111111}.} For protein sequences, by default the program uses the following four shapes: {\tt 111101101110111}, {\tt 1111000101011001111}, {\tt 11101001001000100101111} and {\tt 11101001000010100010100111}. These seeds were suggested in \cite{Ilie:2011fk}, see \url{http://www.biomedcentral.com/content/supplementary/1471-2164-12-280-s1.pdf}. \item[\itt{--maxHitsPerSeed}] Use to specify the maximum number of hits per seed. The program uses this to calculate a maximum number of hits per hash value. \item[\itt{--proteinReduct}] Use this to specify the alphabet reduction in the case of protein reference sequences. By default, the program reduces amino acids to 8 different letters, grouped as follows: [LVIMC] [AG] [ST] [P] [FYW] [EDNQ] [KR] [H]. This is referred to as the \iit{BLOSUM50\_8} reduction in \MALT and was suggested in \cite{Murphy2000}. \end{itemize} MALT is able to generate RMA files that can be directly opened in MEGAN. \begin{itemize} \setlength{\itemindent}{30pt} \item[\itt{--classify}] Use this option to determine which classifications should be computed, such as Taxonomy, EGGNOG, INTERPRO2GO, KEGG and/or SEED. \end{itemize} There are numerous options that can be used to provide mapping files to \program{malt-build} for classification support. These are used by the program to map reference sequences or genes to taxonomic and/or functional classes. \begin{itemize} \setlength{\itemindent}{30pt} \item[\itt{-g2taxonomy}] \itt{-a2taxonomy} \itt{-s2taxonomy} Use to specify mapping files to map reference sequences to taxonomic identifiers (NCBI taxon integer ids). Use {\tt -g2taxonomy} for a file mapping GI numbers to taxon ids. Use {\tt -r2taxonomy} for a file mapping RefSeq identifiers to taxon ids. Use {\tt -s2taxonomy} for a file that maps \pconcept{synonyms} to taxon ids. A synonym is any word that may occur in the header line of a reference sequence. \item[\itt{-g2interpro2go}] \itt{-r2interpro2go} \itt{-s2interpro2go} Use to specify mapping files to map reference sequences to InterPro numbers \cite{GeneOntology2000,Mitchell2015} . The detailed usage of three different options is analogous to above. \item[\itt{-g2seed}] \itt{-r2seed} \itt{-s2seed} Use to specify mapping files to map reference sequences to SEED \cite{SEED2005} classes. Unfortunately, the SEED classification does not assign numerical identifiers to classes. As a work-around, \program{malt-build} uses the numerical identifiers defined and used by \irm{MEGAN} \cite{MEGAN2011}. The detailed usage of three different options is analogous to above. \item[\itt{-g2eggnog}] \itt{-r2eggnog} \itt{-s2eggnog} Use to specify mapping files to map reference sequences to COG and NOG \cite{Tatusov1997,eggNOG} classes. Unfortunately, COG's and NOG's do not share the same space of numerical identifiers. As a work-around, \program{malt-build} uses the numerical identifiers defined and used by \irm{MEGAN} \cite{MEGAN2011}. The detailed usage of three different options is analogous to above. \item[\itt{-g2kegg}] \itt{-r2kegg} \itt{-s2kegg} Use to specify mapping files to map reference sequences to KEGG KO numbers \cite{Kanehisa2000} . The detailed usage of three different options is analogous to above. \ignore{ \item[\itt{-gif}] Use this option specify a \concept{gene information file}. Such a file assigns maps genes to intervals in reference sequences, as described below. This is usually used when the reference sequences are genomes. } \end{itemize} There are a couple of other options: \begin{itemize} \setlength{\itemindent}{30pt} \item[\itt{--firstWordOnly}] Use to specify to save only the first word of each reference header. Default value: false. \item[\itt{--random}] Use to specify the seed used by the random number generator. \item[\itt{--verbose}] Use to run program in verbose mode. \item[\itt{--help}] Report command-line usage. \end{itemize} \begin{figure}[h] {\tiny \begin{verbatim} SYNOPSIS MaltBuild [options] DESCRIPTION Build an index for MALT (MEGAN alignment tool) OPTIONS Input: -i, --input [string(s)] Input reference file(s). Mandatory option. -s, --sequenceType [string] Sequence type. Mandatory option. Legal values: DNA, Protein Output: -d, --index [string] Name of index directory. Mandatory option. Performance: -t, --threads [number] Number of worker threads. Default value: 8. -st, --step [number] Step size used to advance seed, values greater than 1 reduce index size and sensitivity. Default value: 1. Seed: -ss, --shapes [string(s)] Seed shape(s). Default value(s): default. -mh, --maxHitsPerSeed [number] Maximum number of hits per seed. Default value: 1000. -pr, --proteinReduct [string] Name or definition of protein alphabet reduction (BLOSUM50_10,BLOSUM50_11,BLOSUM50_15,BLOSUM50_4,BLOSUM50_8,DIAMOND_11,GBMR4,HSDM17,MALT_10,SDM12,UNREDUCED). Default value: DIAMOND_11. Classification: -c, --classify [string(s)] Classifications (any of EGGNOG INTERPRO2GO KEGG SEED Taxonomy). Mandatory option. -g2eggnog, --gi2eggnog [string] GI-to-EGGNOG mapping file. -r2eggnog, --ref2eggnog [string] RefSeq-to-EGGNOG mapping file. -s2eggnog, --syn2eggnog [string] Synonyms-to-EGGNOG mapping file. -g2interpro2go, --gi2interpro2go [string] GI-to-INTERPRO2GO mapping file. -r2interpro2go, --ref2interpro2go [string] RefSeq-to-INTERPRO2GO mapping file. -s2interpro2go, --syn2interpro2go [string] Synonyms-to-INTERPRO2GO mapping file. -g2kegg, --gi2kegg [string] GI-to-KEGG mapping file. -r2kegg, --ref2kegg [string] RefSeq-to-KEGG mapping file. -s2kegg, --syn2kegg [string] Synonyms-to-KEGG mapping file. -g2seed, --gi2seed [string] GI-to-SEED mapping file. -r2seed, --ref2seed [string] RefSeq-to-SEED mapping file. -s2seed, --syn2seed [string] Synonyms-to-SEED mapping file. -g2taxonomy, --gi2taxonomy [string] GI-to-Taxonomy mapping file. -a2taxonomy, --ref2taxonomy [string] Accession-to-Taxonomy mapping file. -s2taxonomy, --syn2taxonomy [string] Synonyms-to-Taxonomy mapping file. -tn, --parseTaxonNames Parse taxon names. Default value: true. -gif, -geneInfoFile [string] File containing gene information. Other: -fwo, --firstWordOnly Save only first word of reference header. Default value: false. -rns, --random [number] Random number generator seed. Default value: 666. -hsf, --hashScaleFactor [number] Hash table scale factor. Default value: 0.9. -v, --verbose Echo commandline options and be verbose. Default value: false. -h, --help Show program usage and quit. \end{verbatim} } \caption{Summary of command-line usage of malt-build.}\label{fig:malt-build-usage} \end{figure} \FloatBarrier %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% \mysection{The MALT analyzer} %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% In summary, the program \pprogram{malt-run} is used to align one or more files of input sequences (DNA or proteins) against an index representing a collection of reference {DNA or} protein sequences. In a preprocessing step, the index is computed using the \program{malt-build}, as described above. Depending on the type of input and reference sequences, the program can be be run in {BLASTN,} BLASTP or BLASTX mode. The \program{malt-run} program is controlled by command-line options (see Figure~\ref{fig:malt-run-usage}). The first options specifies the program mode and alignment type. \begin{itemize} \setlength{\itemindent}{30pt} \item[\itt{--mode}] Use this to run the program in {\pconcept{BlastN mode},} \pconcept{BlastP mode} or \pconcept{BlastX mode}, that is, to align {DNA and DNA,} protein and protein, or DNA reads against protein references, respectively. Obviously, the former mode can only be used if the employed index contains DNA sequences whereas the latter two modes are only applicable to an index based on protein reference sequences. \item[\itt{--alignmentType}] Use this to specify the type of alignments to be performed. By default, this is set to \itt{Local} and the program performs \pconcept{local alignment} just like BLAST programs do. Alternatively, this can be set to \itt{SemiGlobal}, in which case the program will perform \pconcept{semi global alignment} in which reads are aligned end-to-end. \end{itemize} There are two options for specifying the input. \begin{itemize} \setlength{\itemindent}{30pt} \item [\itt{--inFile}] Use this to specify all input files. Input files must be in FastA or FastQ format and may be gzipped, in which case their names must end on \itt{.gz}. \item[\itt{--index}] Use this to specify the directory that contains the index built by \program{malt-build}. \end{itemize} There is a number of options for specifying the output generated by the program. \begin{itemize} \setlength{\itemindent}{30pt} \item[\itt{--output}] Use to specify the names or locations of the output RMA files. If a single directory is specified, then one output file per input file is written to the specified directory. Alternatively, if one or more output files are named, then the number of output files must equal the number of input files, in which case the output for the first input file is written to first output file, etc. \item[\itt{--includeUnaligned}] Use this to ensure that all unaligned queries are placed into the output RMA file. By default, only queries that have an alignment are included in the output RMA file. \item[\itt{--alignments}] Use to specify the files to which alignments should be written. If a single directory is specified, then one output file per input file is written to the specified directory. Alternatively, if one or more output files are named, then the number of output files must equal the number of input files, in which case the output for the first input file is written to first output file, etc. If the argument is the special value \itt{STDOUT} then output is written to standard-output rather than to a file. If this option is not supplied, then the program will not output any matches. \item[\itt{--format}] Determines the format used to report alignments. The default format is \itt{SAM}. Other choices are \itt{Text} (full text BLAST matches) and \itt{Tab} (tabulated BLAST format). \item[\itt{--gzipOutput}] Use this to specify whether alignment output should be gzipped. Default is true. \item[\itt{--outAligned}] Use this to specify that all reads that have at least one significant alignment to some reference sequence should be saved. File specification possibilities as for \itt{--alignments}. \item[\itt{--samSoftClip}] Request that SAM output uses soft clipping. \item[\itt{--sparseSAM}] Request a sparse version of SAM output. This is faster and uses less memory, but the files are not necessary compatible with other SAM processing tools. \item[\itt{--gzipAligned}] Compress aligned reads output using gzip. Default value: true. \item[\itt{--outUnaligned}] Use this to specify that all reads that do not have any significant alignment to any reference sequence should be saved. File specification possibilities as for \itt{--alignments}. \item[\itt{ --gzipUnaligned}] Compress unaligned reads output using gzip. Default value: true. \end{itemize} There are three performance-related options: \begin{itemize} \setlength{\itemindent}{30pt} \item[\itt{--threads}] Use to set the number of threads to use in parallel computations. Default is 8. Set this to the number of available cores. -rqc, Cache results for replicated queries. \item[\itt{--memoryMode}] Load all indices into memory, load indices page by page when needed or use memory mapping (load, page or map). \item[\itt{--maxTables}] Use to set the maximum number of seed tables to use (0=all). Default value: 0. \item[\itt{--replicateQueryCache}] Use to turn on caching of replicated queries. This is especially useful for processing 16S datasets in which identical sequences occur multiple times. Turning on this feature does not change the output of the program, but can cause a significant speed-up. Default value: false. \end{itemize} The most important performance-related option is the maximum amount of memory that \program{malt-run} is allowed to use. This cannot be set from within the program but rather is set during installation of the software. The following options are used to filter matches by significance. Matches that do not meet all criteria specified are completely ignored. \begin{itemize} \setlength{\itemindent}{30pt} \item[\itt{--minBitScore}] Minimum bit disjointScore. Default value: 50.0. \item[\itt{--maxExpected}] Maximum expected disjointScore. Default value: 1.0. \item[{\itt{--minPercentIdentity}}] Minimum percent identity. Default value: 0.0. \item[{\itt{--maxAlignmentsPerQuery}}] Maximum number of alignments per query. Default value: 100. \item[{\itt{ --maxAlignmentsPerRef}}] Maximum number of (non-overlapping) alignments per reference. Default value: 1. \MALT reports up to this many best scoring matches for each hit reference sequence. \end{itemize} { There are a number of options that are specific to the \concept{BlastN mode}. They are used to specify scoring and are also used in the computation of expected values. \begin{itemize} \setlength{\itemindent}{30pt} \item[\itt{--matchScore}] Use to specify the alignment match disjointScore. Default value: 2. \item[\itt{--mismatchScore}] Use to specify the alignment mis-match disjointScore. Default value: -3. \item[\itt{--setLambda}] Parameter Lambda \index{Lambda parameter} for \irm{BLASTN statistics}. Default value: 0.625. \item[\itt{--setK}] Parameter K \index{K parameter} for BLASTN statistics. Default value: 0.41. \end{itemize} } For \concept{BlastP mode} and \concept{BlastX mode} the user need only specify a substitution matrix. The Lambda and K values are set automatically. \begin{itemize} \setlength{\itemindent}{30pt} \item[\itt{--subMatrix}] Use to specify the protein substitution matrix to use. Default value: {\tt BLOSUM62}. Legal values: \itt{BLOSUM45}, \itt{BLOSUM50}, \itt{BLOSUM62}, \itt{BLOSUM80}, \itt{BLOSUM90}. \end{itemize} If the query sequences are DNA (or RNA) sequences, that is, if the program is running in {\concept{BlastN mode}} or \concept{BlastX mode}, then the following options are available. \begin{itemize} \setlength{\itemindent}{30pt} \item[\itt{--forwardOnly}] Use to align query forward strand only. Default value: false. \item[\itt{ --reverseOnly}] Use to align query reverse strand only. Default value: false. \end{itemize} The program uses the LCA algorithm \cite{MEGAN2007} to assign reads to taxa. There are a number of options that control this. \begin{itemize} \setlength{\itemindent}{30pt} \item[\itt{lca\_taxonomy}] Use to specify that the LCA algorithm should be applied to the taxonomy classification. Similar switches are available to turn on the use of the LCA algorithm for other classifications. But using the LCA algorithms only makes sense when providing additional taxonomic classifications such as the RDP tree. \item[\itt{--topPercent}] Use to specify the \pconcept{top percent} value for LCA algorithm. Default value is 10\%. For each read, only those matches are used for taxonomic placement whose bit disjointScore is within 10\% of the best disjointScore for that read. \item[\itt{--minSupport}] Use to specify the \pconcept{min support} value for the LCA algorithm. \end{itemize} There are a number of options that control the heuristics used by \program{malt-run}. \begin{itemize} \setlength{\itemindent}{30pt} \item[{\itt{--maxSeedsPerFrame}}] Maximum number of seed matches per offset per read frame. Default value: 100. \item[{\itt{--maxSeedsPerRef}}] Maximum number of seed matches per read and reference. Default value: 20. \item[\itt{ --seedShift}] Seed shift. Default value: 1. \end{itemize} The program uses a banded-aligner as described in \cite{ChaoPM92}. There are a number of associated options. \begin{itemize} \setlength{\itemindent}{30pt} \item[\itt{--gapOpen}] Use this to specify the gap open penalty. Default value: 7. \item[\itt{--gapExtend}] Use this to specify gap extension penalty. Default value: 3. \item[\itt{--band}] Use this to specify width/2 for banded alignment. Default value: 4. \end{itemize} The are a couple of other options: \begin{itemize} \setlength{\itemindent}{30pt} \item[\itt{--replicateQueryCacheBits}] Specify the number of bits used to cache replicate queries (default is 20). \item[\itt{--verbose}] Use to run program in verbose mode. \item[\itt{--help}] Report command-line usage. \end{itemize} \begin{figure}[h] {\tiny \begin{verbatim} SYNOPSIS MaltRun [options] DESCRIPTION Align sequences using MALT (MEGAN alignment tool) OPTIONS Mode: -m, --mode [string] Program mode. Mandatory option. Legal values: Unknown, BlastN, BlastP, BlastX, Classifier -at, --alignmentType [string] Type of alignment to be performed. Default value: Local. Legal values: Local, SemiGlobal Input: -i, --inFile [string(s)] Input file(s) containing queries in FastA or FastQ format. Mandatory option. -d, --index [string] Index directory as generated by MaltBuild. Mandatory option. Output: -o, --output [string(s)] Output RMA file(s) or directory. -iu, --includeUnaligned Include unaligned queries in RMA output file. Default value: false. -a, --alignments [string(s)] Output alignment file(s) or directory or STDOUT. -f, --format [string] Alignment output format. Default value: SAM. Legal values: SAM, Tab, Text -za, --gzipAlignments Compress alignments using gzip. Default value: true. -ssc, --samSoftClip Use soft clipping in SAM files (BlastN mode only). Default value: false. -sps, --sparseSAM Produce sparse SAM format (smaller, faster, suitable for MEGAN). Default value: false. -oa, --outAligned [string(s)] Aligned reads output file(s) or directory or STDOUT. -zal, --gzipAligned Compress aligned reads output using gzip. Default value: true. -ou, --outUnaligned [string(s)] Unaligned reads output file(s) or directory or STDOUT. -zul, --gzipUnaligned Compress unaligned reads output using gzip. Default value: true. Performance: -t, --numThreads [number] Number of worker threads. Default value: 8. -mem, --memoryMode [string] Memory mode. Default value: load. Legal values: load, page, map -mt, --maxTables [number] Set the maximum number of seed tables to use (0=all). Default value: 0. -rqc, --replicateQueryCache Cache results for replicated queries. Default value: false. Filter: -b, --minBitScore [number] Minimum bit disjointScore. Default value: 50.0. -e, --maxExpected [number] Maximum expected disjointScore. Default value: 1.0. -id, --minPercentIdentity [number] Minimum percent identity. Default value: 0.0. -mq, --maxAlignmentsPerQuery [number] Maximum number of alignments per query. Default value: 25. -mrf, --maxAlignmentsPerRef [number] Maximum number of (non-overlapping) alignments per reference. Default value: 1. BlastN parameters: -ma, --matchScore [number] Match disjointScore. Default value: 2. -mm, --mismatchScore [number] Mismatch disjointScore. Default value: -3. -la, --setLambda [number] Parameter Lambda for BLASTN statistics. Default value: 0.625. -K, --setK [number] Parameter K for BLASTN statistics. Default value: 0.41. BlastP and BlastX parameters: -psm, --subMatrix [string] Protein substitution matrix to use. Default value: BLOSUM62. Legal values: BLOSUM45, BLOSUM50, BLOSUM62, BLOSUM80, BLOSUM90 DNA query parameters: -fo, --forwardOnly Align query forward strand only. Default value: false. -ro, --reverseOnly Align query reverse strand only. Default value: false. LCA: -wLCA, --useWeightedLCA Use the weighted-LCA algorithm. Default value: false. -wLCAP, --lcaCoveragePercent [number] Set the weighted-LCA percentage of weight to cover. Default value: 80.0. -top, --topPercent [number] Top percent value for LCA algorithm. Default value: 10.0. -supp, --minSupportPercent [number] Min support value for LCA algorithm as a percent of assigned reads (0==off). Default value: 0.001. -sup, --minSupport [number] Min support value for LCA algorithm (overrides --minSupportPercent). Default value: 1. -mpi, --minPercentIdentityLCA [number] Min percent identity used by LCA algorithm. Default: 0. -mif, --useMinPercentIdentityFilterLCA Use min percent identity assignment filter (Species 99%, Genus 9\%, Family 95%, Order 90%, Class 85%, Phylum 80%). -mag, --magnitudes Reads have magnitudes (to be used in taxonomic or functional analysis). Default value: false. Heuristics: -spf, --maxSeedsPerFrame [number] Maximum number of seed matches per offset per read frame. Default value: 100. -spr, --maxSeedsPerRef [number] Maximum number of seed matches per read and reference. Default value: 20. -sh, --seedShift [number] Seed shift. Default value: 1. Banded alignment parameters: -go, --gapOpen [number] Gap open penalty. Default value: 11. -ge, --gapExtend [number] Gap extension penalty. Default value: 1. -bd, --band [number] Band width/2 for banded alignment. Default value: 4. Other: -rqcb, --replicateQueryCacheBits [number] Bits used for caching replicate queries (size is then 2^bits). Default value: 20. -v, --verbose Echo commandline options and be verbose. Default value: false. -h, --help Show program usage and quit.\end{verbatim} } \caption{Summary of command-line usage of {\tt malt-run}.}\label{fig:malt-run-usage} \end{figure} \FloatBarrier %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% {\small \bibliographystyle{plain} \bibliography{compbio-2012} } \printindex \end{document} malt-0.5.2/tex/manual/versioninfo.tex000066400000000000000000000000261400455127600176150ustar00rootroot00000000000000\def\VERSION{{0.5.2}} malt-0.5.2/tex/manual/versioninfo_new.tex000066400000000000000000000000261400455127600204660ustar00rootroot00000000000000\def\VERSION{{0.5.2}} malt-0.5.2/tex/manual/versioninfo_old.tex000066400000000000000000000000261400455127600204530ustar00rootroot00000000000000\def\VERSION{{0.5.1}}