pax_global_header00006660000000000000000000000064147707530170014525gustar00rootroot0000000000000052 comment=7e235d29b1ab60f2e4788e7c819d7c0277aa2954 libformfactor-v0.3.2-7e235d29b1ab60f2e4788e7c819d7c0277aa2954/000077500000000000000000000000001477075301700220615ustar00rootroot00000000000000libformfactor-v0.3.2-7e235d29b1ab60f2e4788e7c819d7c0277aa2954/.clang-format000066400000000000000000000007101477075301700244320ustar00rootroot00000000000000BasedOnStyle: LLVM AccessModifierOffset: -4 AllowAllConstructorInitializersOnNextLine: true AllowShortFunctionsOnASingleLine: Inline AllowShortIfStatementsOnASingleLine: false BreakBeforeBinaryOperators: NonAssignment BreakBeforeBraces: WebKit BreakConstructorInitializers : BeforeComma ColumnLimit: 100 ConstructorInitializerAllOnOneLineOrOnePerLine: true IndentCaseLabels: false IndentWidth: 4 MaxEmptyLinesToKeep: 2 PointerAlignment: Left UseTab: Never libformfactor-v0.3.2-7e235d29b1ab60f2e4788e7c819d7c0277aa2954/.clang-tidy000066400000000000000000000103621477075301700241170ustar00rootroot00000000000000--- # # This is the BornAgain configuration for clang-tidy. # # To invoke clang-tidy, run # cmake -DBORNAGAIN_TIDY=ON -DBORNAGAIN_PYTHON=OFF -DBORNAGAIN_GUI=OFF .. # make # # Below, we select all checks ('*'), then deselect quite a number of them. # # As we are not aware of an official way to insert comments in a long string literal, # we do a dirty little trick: we write comments as if they were no-check specifiers. # Checks: '*, -SectionComment_We_disagree_with_the_following_checks__They_shall_remain_permanently_disabled, -*-braces-around-statements, -*-convert-member-functions-to-static, -*-implicit-bool-conversion, -*-magic-numbers, -*-named-parameter, -*-trailing-return*, -*-uppercase-literal-suffix, -*nodiscard, -abseil-string-find-str-contains, -bugprone-branch-clone, -bugprone-suspicious-include, -cert-err58-cpp, -cert-err61-cpp, -cert-msc30-c*, -cert-msc50-cpp, -clang-analyzer-alpha*, -clang-analyzer-alpha.deadcode.UnreachableCode, -clang-analyzer-security.insecureAPI.strcpy, -cppcoreguidelines-init-variables, -cppcoreguidelines-macro-usage, -cppcoreguidelines-non-private-member-variables-in-classes, -cppcoreguidelines-pro-bounds-constant-array-index, -cppcoreguidelines-pro-bounds-pointer-arithmetic, -cppcoreguidelines-pro-type-cstyle-cast, -cppcoreguidelines-pro-type-member-init, -cppcoreguidelines-pro-type-reinterpret-cast, -cppcoreguidelines-pro-type-vararg, -cppcoreguidelines-slicing, -fuchsia-default-arguments-calls, -fuchsia-default-arguments-declarations, -fuchsia-overloaded-operator, -fuchsia-statically-constructed-objects, -fuchsia-trailing-return, -google-build-using-namespace, -google-default-arguments, -google-explicit-constructor, -google-readability-casting, -google-readability-todo, -google-runtime-int, -hicpp-exception-baseclass, -hicpp-explicit-conversions, -hicpp-member-init, -hicpp-signed-bitwise, -hicpp-vararg, -llvmlibc-callee-namespace, -llvmlibc-implementation-in-namespace, -llvmlibc-restrict-system-libc-headers, -misc-no-recursion, -misc-throw-by-value-catch-by-reference, -performance-inefficient-string-concatenation, -performance-noexcept-move-constructor, -performance-no-automatic-move, -performance-unnecessary-value-param, -readability-use-anyofallof, -SectionComment_To_be_manually_checked_from_time_to_time, -readability-isolate-declaration, -performance-inefficient-vector-operation, -performance-unnecessary-copy-initialization, -*-move-const-arg, -modernize-use-default-member-init, -readability-redundant-member-init, -SectionComment_Disabled_unless_3rdparty_libraries_are_improved, -*avoid-goto, -*special-member-functions, -clang-analyzer-cplusplus.NewDeleteLeaks, -cppcoreguidelines-avoid-non-const-global-variables, -cppcoreguidelines-pro-bounds-array-to-pointer-decay, -hicpp-no-array-decay, -llvm-include-order, -llvm-namespace-comment, -readability-redundant-access-specifiers, -SectionComment_Resolving_the_following_checks_would_be_too_much_work_right_now, -*avoid-c-arrays, -*narrowing-conversions, -cppcoreguidelines-owning-memory, -bugprone-unused-return-value, -bugprone-parent-virtual-call, -SectionComment_Temporarily_disabled_checks__We_need_to_investigate_them_one_by_one, -bugprone-copy-constructor-init, -bugprone-exception-escape, -bugprone-misplaced-widening-cast, -clang-analyzer-core.CallAndMessage, -clang-analyzer-optin.cplusplus.VirtualCall, -cppcoreguidelines-pro-type-const-cast, -cppcoreguidelines-pro-type-static-cast-downcast, -google-runtime-references, -misc-non-private-member-variables-in-classes, -modernize-loop-convert, -SectionComment_Automizable__To_be_kept_satisfied, *-use-auto, *-use-emplace, *-use-equals-default, *-use-nullptr, *-use-override, cppcoreguidelines-explicit-virtual-functions, google-readability-avoid-underscore-in-googletest-name, hicpp-noexcept-move, llvm-qualified-auto, misc-uniqueptr-reset-release, modernize-avoid-bind, modernize-make-unique, modernize-pass-by-value, modernize-raw-string-literal, modernize-use-using, performance-for-range-copy, readability-avoid-const-params-in-decls, readability-const-return-type, readability-non-const-parameter, readability-container-size-empty, readability-delete-null-pointer, readability-inconsistent-declaration-parameter-name, readability-qualified-auto, readability-simplify-boolean-expr, ' # keep the closing quotation marklibformfactor-v0.3.2-7e235d29b1ab60f2e4788e7c819d7c0277aa2954/.gitignore000066400000000000000000000002141477075301700240460ustar00rootroot00000000000000*~ *.bak *.patch *.tmp *.user # doxygen generated dox/ latex/ html/ # build directories build/ cover/ debug/ tidy/ demo/build demo/debug libformfactor-v0.3.2-7e235d29b1ab60f2e4788e7c819d7c0277aa2954/.gitlab-ci.yml000066400000000000000000000025041477075301700245160ustar00rootroot00000000000000stages: - build mac_x64: tags: - mac_x64_cloud stage: build script: &macbuild - OPTDIR=/Users/Shared/Software/scg - mkdir build - cd build - cmake -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX="$OPTDIR" -DCMAKE_PREFIX_PATH="$OPTDIR" -S .. - make - ctest --output-on-failure - if [ "$CI_COMMIT_REF_NAME" == "main" ]; then make install; fi mac_arm: tags: - mac_arm stage: build script: *macbuild windows: tags: - Windows stage: build script: - cmake -G "Visual Studio 17 2022" -A x64 -T host=x64 -DCMAKE_INSTALL_PREFIX=C:/opt/x64 -S . -B build - cd build - cmake --build . --config Release - ctest -C Release --output-on-failure - if ("$CI_COMMIT_REF_NAME" -eq "main") { cmake --install . ; } - cpack . -G ZIP artifacts: paths: - build/*zip expire_in: 3 days native_Debian_clang: tags: &native - Debian before_script: - export CC=gcc; export CXX=g++ - cmake --version - clang --version stage: build script: &native_scr - pwd && ls - mkdir build - cd build - cmake -DWERROR=ON -DPEDANTIC=ON .. - make - ctest --output-on-failure - if [ "$CI_COMMIT_REF_NAME" == "main" ]; then make install; fi - cpack --config CPackConfig.cmake -G TGZ artifacts: paths: - build/*gz expire_in: 3 days libformfactor-v0.3.2-7e235d29b1ab60f2e4788e7c819d7c0277aa2954/CHANGELOG000066400000000000000000000026231477075301700232760ustar00rootroot00000000000000libformfactor-v0.3.2, released 26mar25, for use with BornAgain-22 - Support deployment to Mac through Brew. - Minor changes to CI script. libformfactor-v0.3.1, released 02aug23 - CMake: removed option PORTABLE and compiler flag -march (unavailable on some architectures) libformfactor-v0.3.0, released 14jun23 - Corrected version number in formfactorConfigVersion.cmake libformfactor-v0.2.1, released 14jun23 - Corrected hard-coded version number libformfactor-v0.2.0, released 29mar23 - Changed functionality: - Rotate octahedron. - Changed API: - Rename IPolyhedron -> IBody to prepare for generalizations. - Change factory mechanism, now all in Make.h. - Additions: - Add ff::Box. - Provide fcts for introspection of topology and geometry - Provide 'inside' tests (contributed by Ludwig Jäck) - Provide clipping function (for convex polyhedra only; contributed by Ludwig Jäck) - Additions in git history: - Serialization to Wavefront object file (for visualization using e.g. Blender), before https://jugit.fz-juelich.de/mlz/libformfactor/-/merge_requests/53 libformfactor-v0.1.3, released 18feb22 - Correct CMake script libformfactor-v0.1.2, released 17feb22 - Correct CMake scripts libformfactor-v0.1.1, released 25jan22 - Simplify API, function formfactor now refers to geometric center - Add demos libformfactor-v0.1.0, released 29nov21 - Initial release, split off from BornAgain libformfactor-v0.3.2-7e235d29b1ab60f2e4788e7c819d7c0277aa2954/CMakeLists.txt000066400000000000000000000057221477075301700246270ustar00rootroot00000000000000cmake_minimum_required(VERSION 3.20 FATAL_ERROR) project(formfactor VERSION 0.3.2 LANGUAGES CXX) set(CMAKE_MODULE_PATH ${CMAKE_SOURCE_DIR}/cmake) include(PreventInSourceBuilds) set(CMAKE_PROJECT_DESCRIPTION # for display in installer "libformfactor - computes Fourier shape transforms.") ## Options. if(NOT DEFINED BUILD_SHARED_LIBS) option(BUILD_SHARED_LIBS "Build as shared library" ON) endif() ## Compiler settings. set(CMAKE_CXX_STANDARD 20) if(MSVC) set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /MP") set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS}") set(MS_NOWARN "/wd4018 /wd4068 /wd4101 /wd4244 /wd4267 /wd4305 /wd4715 /wd4996") set(MS_D "-D_CRT_SECURE_NO_WARNINGS -D_SILENCE_TR1_NAMESPACE_DEPRECATION_WARNING") set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS} ${MS_NOWARN} ${MS_D}") set(CTEST_CONFIGURATION_TYPE "${JOB_BUILD_CONFIGURATION}") set(CMAKE_WINDOWS_EXPORT_ALL_SYMBOLS ON) set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${PROJECT_BINARY_DIR}/bin/$) else() option(WERROR "Treat warnings as errors" OFF) option(PEDANTIC "Compile with pedantic warnings" ON) if(PEDANTIC) add_compile_options(-pedantic -Wall) endif() if(WERROR) add_compile_options(-Werror) endif() add_compile_options(-fno-omit-frame-pointer) if (CMAKE_BUILD_TYPE MATCHES Debug) add_compile_options(-g) else() add_compile_options(-O3) endif() set(CMAKE_RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/bin") set(CMAKE_LIBRARY_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/lib") endif() ## Dependences. find_package(LibHeinz REQUIRED) message(STATUS "LibHeinz: found=${LibHeinz_FOUND}, include_dirs=${LibHeinz_INCLUDE_DIR}, " "version=${LibHeinz_VERSION}") ## Source directory. add_subdirectory(ff) ## Tests. include(CTest) add_subdirectory(test) ## CPack settings. include(InstallRequiredSystemLibraries) set(CPACK_RESOURCE_FILE_LICENSE "${CMAKE_CURRENT_SOURCE_DIR}/LICENSE") set(CPACK_PACKAGE_VERSION_MAJOR "${formfactor_VERSION_MAJOR}") set(CPACK_PACKAGE_VERSION_MINOR "${formfactor_VERSION_MINOR}") set(CPACK_PACKAGE_VERSION_PATCH "${formfactor_VERSION_PATCH}") set(CPACK_SOURCE_GENERATOR "TGZ") include(CPack) ## Config files. include(CMakePackageConfigHelpers) configure_package_config_file("${CMAKE_CURRENT_SOURCE_DIR}/Config.cmake.in" "${CMAKE_CURRENT_BINARY_DIR}/formfactorConfig.cmake" INSTALL_DESTINATION cmake NO_SET_AND_CHECK_MACRO NO_CHECK_REQUIRED_COMPONENTS_MACRO ) write_basic_package_version_file( "${CMAKE_CURRENT_BINARY_DIR}/formfactorConfigVersion.cmake" VERSION "${formfactor_VERSION}" COMPATIBILITY AnyNewerVersion ) install(FILES "${PROJECT_BINARY_DIR}/formfactorConfig.cmake" "${PROJECT_BINARY_DIR}/formfactorConfigVersion.cmake" DESTINATION cmake) ## Export targets. install(EXPORT formfactorTargets FILE formfactorTargets.cmake DESTINATION cmake) export(EXPORT formfactorTargets FILE "${CMAKE_CURRENT_BINARY_DIR}/formfactorTargets.cmake") libformfactor-v0.3.2-7e235d29b1ab60f2e4788e7c819d7c0277aa2954/Config.cmake.in000066400000000000000000000002121477075301700246700ustar00rootroot00000000000000@PACKAGE_INIT@ set(formfactor_INCLUDE_DIR "@CMAKE_INSTALL_PREFIX@/include") include("${CMAKE_CURRENT_LIST_DIR}/formfactorTargets.cmake") libformfactor-v0.3.2-7e235d29b1ab60f2e4788e7c819d7c0277aa2954/LICENSE000066400000000000000000001043651477075301700230770ustar00rootroot00000000000000 GNU GENERAL PUBLIC LICENSE Version 3, 29 June 2007 Copyright (C) 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 How to Apply These Terms to Your New Programs If you develop a new program, and you want it to be of the greatest possible use to the public, the best way to achieve this is to make it free software which everyone can redistribute and change under these terms. To do so, attach the following notices to the program. It is safest to attach them to the start of each source file to most effectively state the exclusion of warranty; and each file should have at least the "copyright" line and a pointer to where the full notice is found. libformfactor Copyright (C) 2021 mlz 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 . Also add information on how to contact you by electronic and paper mail. If the program does terminal interaction, make it output a short notice like this when it starts in an interactive mode: Copyright (C) 2021 mlz This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. This is free software, and you are welcome to redistribute it under certain conditions; type `show c' for details. The hypothetical commands `show w' and `show c' should show the appropriate parts of the General Public License. Of course, your program's commands might be different; for a GUI interface, you would use an "about box". You should also get your employer (if you work as a programmer) or school, if any, to sign a "copyright disclaimer" for the program, if necessary. For more information on this, and how to apply and follow the GNU GPL, see . The GNU General Public License does not permit incorporating your program into proprietary programs. If your program is a subroutine library, you may consider it more useful to permit linking proprietary applications with the library. If this is what you want to do, use the GNU Lesser General Public License instead of this License. But first, please read . libformfactor-v0.3.2-7e235d29b1ab60f2e4788e7c819d7c0277aa2954/README.md000066400000000000000000000011311477075301700233340ustar00rootroot00000000000000# libformfactor A C++ library for the efficient computation of scattering form factors (Fourier shape transforms) of arbitrary polyhedra according to Wuttke, [J Appl Cryst 54, 580-587 (2021)](https://doi.org/10.1107/S1600576721001710). The library is in directory ff/. Tests are in directory test/. To build the binaries and run the tests, do ``` mkdir build cd build cmake .. make ctest make install ``` Usage is demonstrated in directory demo/. To build and run example code that prints q vs |F(q)| for some sequences of q vectors, do ``` cd demo mkdir build cd build cmake .. make octahedron ```libformfactor-v0.3.2-7e235d29b1ab60f2e4788e7c819d7c0277aa2954/cmake/000077500000000000000000000000001477075301700231415ustar00rootroot00000000000000libformfactor-v0.3.2-7e235d29b1ab60f2e4788e7c819d7c0277aa2954/cmake/PreventInSourceBuilds.cmake000066400000000000000000000011371477075301700304030ustar00rootroot00000000000000# - Prevent in-source builds. function(prevent_in_source_builds) # make sure the user doesn't play dirty with symlinks get_filename_component(srcdir "${CMAKE_SOURCE_DIR}" REALPATH) get_filename_component(bindir "${CMAKE_BINARY_DIR}" REALPATH) # disallow in-source builds if("${srcdir}" STREQUAL "${bindir}") message(FATAL_ERROR "\ CMake must not to be run in the source directory. \ Rather create a dedicated build directory and run CMake there. \ To clean up after this aborted in-place compilation: rm -r CMakeCache.txt CMakeFiles ") endif() endfunction() prevent_in_source_builds() libformfactor-v0.3.2-7e235d29b1ab60f2e4788e7c819d7c0277aa2954/demo/000077500000000000000000000000001477075301700230055ustar00rootroot00000000000000libformfactor-v0.3.2-7e235d29b1ab60f2e4788e7c819d7c0277aa2954/demo/CMakeLists.txt000066400000000000000000000014541477075301700255510ustar00rootroot00000000000000cmake_minimum_required(VERSION 3.15 FATAL_ERROR) project(demo_using_formfactor VERSION 0.1.2 LANGUAGES CXX) ## Compiler settings. option(WERROR "Treat warnings as errors" OFF) set(CMAKE_CXX_STANDARD 17) if(WIN32) set(CMAKE_WINDOWS_EXPORT_ALL_SYMBOLS ON) message(STATUS "compiling for Windows") else() option(PEDANTIC "Compile with pedantic warnings" ON) if(PEDANTIC) add_compile_options(-pedantic -Wall) endif() endif() add_compile_options(-O2) if(WERROR) add_compile_options(-Werror) endif() ## Dependencies find_package(LibHeinz REQUIRED CONFIG) find_package(formfactor REQUIRED CONFIG) ## Source set(demos octahedron ) ## Configure executable foreach(app ${demos}) add_executable(${app} "${app}.cpp") target_link_libraries(${app} formfactor) endforeach() libformfactor-v0.3.2-7e235d29b1ab60f2e4788e7c819d7c0277aa2954/demo/octahedron.cpp000066400000000000000000000052411477075301700256410ustar00rootroot00000000000000// ************************************************************************************************ // // libformfactor: efficient and accurate computation of scattering form factors // //! @file demo/octahedron.cpp //! @brief Computes the formfactor of a platonic octahedron //! //! @homepage https://jugit.fz-juelich.de/mlz/libformfactor //! @license GNU General Public License v3 or higher (see LICENSE) //! @copyright Forschungszentrum Jülich GmbH 2022 //! @author Joachim Wuttke, Scientific Computing Group at MLZ (see CITATION) // // ************************************************************************************************ #include "ff/Make.h" #include constexpr double twopi = 6.28318530718; //! Prints list t vs |F(q(t))| for a logarithmic range of t values int main() { std::cout << "# Octahedral form factor, for different q scans\n"; ff::make::Octahedron octahedron(1.); std::cout << "# q vs |F(q)| for q in direction 111, perpendicular to two faces\n"; for (double t = 0.2; t < 200; t *= 1.002) { C3 q(t / sqrt(3), t / sqrt(3), t / sqrt(3)); std::cout << t << " " << std::abs(octahedron.formfactor(q)) << std::endl; } std::cout << std::endl; std::cout << "# q vs |F(q)| for q in direction 110, perpendicular to two edges\n"; for (double t = 0.2; t < 200; t *= 1.002) { C3 q(t / sqrt(2), t / sqrt(2), 0); std::cout << t << " " << std::abs(octahedron.formfactor(q)) << std::endl; } std::cout << std::endl; std::cout << "# q vs |F(q)| for q in direction 345, no special symmetry\n"; for (double t = 0.2; t < 200; t *= 1.002) { C3 q(3 * t / sqrt(50), 4 * t / sqrt(50), 5 * t / sqrt(50)); std::cout << t << " " << std::abs(octahedron.formfactor(q)) << std::endl; } std::cout << std::endl; std::cout << "# q vs |F(q)| for |q|=50, q on grand cercle through 111 and -1,-1,1 directions\n"; const C3 a1(1 / sqrt(3), 1 / sqrt(3), 1 / sqrt(3)); const C3 a2(-1 / sqrt(6), -1 / sqrt(6), 2 / sqrt(6)); for (double t = 0; t < twopi; t += twopi / 500) { C3 q = 50. * (cos(t) * a1 + sin(t) * a2); std::cout << t << " " << std::abs(octahedron.formfactor(q)) << std::endl; } std::cout << std::endl; std::cout << "# q vs |F(q)| for |q|=50, q on grand cercle through 111 and -2,3,-5 directions\n"; const C3 b1(1 / sqrt(3), 1 / sqrt(3), 1 / sqrt(3)); const C3 b2(-8 / sqrt(98), 3 / sqrt(98), 5 / sqrt(98)); for (double t = 0; t < twopi; t += twopi / 500) { C3 q = 50. * (cos(t) * b1 + sin(t) * b2); std::cout << t << " " << std::abs(octahedron.formfactor(q)) << std::endl; } std::cout << std::endl; } libformfactor-v0.3.2-7e235d29b1ab60f2e4788e7c819d7c0277aa2954/devtools/000077500000000000000000000000001477075301700237205ustar00rootroot00000000000000libformfactor-v0.3.2-7e235d29b1ab60f2e4788e7c819d7c0277aa2954/devtools/plot/000077500000000000000000000000001477075301700246765ustar00rootroot00000000000000libformfactor-v0.3.2-7e235d29b1ab60f2e4788e7c819d7c0277aa2954/devtools/plot/plotff.py000066400000000000000000000066041477075301700265500ustar00rootroot00000000000000# script to plot the form factor # after the ROOT window with plots appear, # just save the picture in that format which you need import ROOT from libBornAgainCore import * # define a form factor, I recommend a 10--20 nm diameter #ff = FormFactorFullSphere(10.0*nanometer) #ff = FormFactorSphere(10.0*nanometer, 13.0*nanometer) ff = FormFactorPyramid(13*nanometer, 10.0*nanometer, 60*degree) # volume # I suggest, that the form factor evaluated for q=0 gives the volume # please correct if not zero = cvector_t(0,0,0) V = abs(ff.evaluate_for_q(zero)) # number of bins for qx, qy, qz # I recommend to put at least 400 for a better picture quality nqy = 400 nqz = 400 nqx = 400 # minimum and maximum values for qx, qy qz qymin = -2.0 qymax = 2.0 qzmin = -2.0 qzmax = 2.0 qxmin = -2.0 qxmax = 2.0 # first and last bin for the projection slice # if number of bins=400, then fbin should be set to 200 and lbin to 201 #fbin=200 #lbin=201 # step for qx, qy, qz stepqy = (qymax - qymin)/(nqy-1) stepqz = (qzmax - qzmin)/(nqz-1) stepqx = (qxmax - qxmin)/(nqx-1) # define style of the diagram ROOT.gROOT.SetStyle("Plain") ROOT.gStyle.SetOptStat(0); ROOT.gStyle.SetOptTitle(0); ROOT.gStyle.SetLabelSize(0.06, "xyz"); ROOT.gStyle.SetTitleSize(0.06, "xyz"); #ROOT.gStyle.SetPadRightMargin(0.1) ROOT.gStyle.SetPadLeftMargin(0.18) ROOT.gStyle.SetPadBottomMargin(0.18) t = ROOT.TText() # create ROOT histograms hist = ROOT.TH2D("hist","Sphere:H=R",nqy,qymin,qymax, nqz, qzmin, qzmax) hist2 = ROOT.TH2D("hist2","Sphere:H=R",nqx,qxmin,qxmax, nqy, qymin, qymax) # and fill them with the values for i in range(nqy): qy = qymin + i*stepqy for j in range(nqz): qz = qzmin + j*stepqz k = cvector_t(0,qy,qz) hist.Fill(qy,qz,(abs(ff.evaluate_for_q(k)))**2+1) for i in range(nqy): qy = qymin + i*stepqy for j in range(nqx): qx = qxmin + j*stepqx k = cvector_t(qx,qy,0) hist2.Fill(qx,qy,(abs(ff.evaluate_for_q(k)))**2+1) # create a ROOT canvas and put all plots on it c = ROOT.TCanvas("FormfactorSphere","Formfactor Sphere", 1000,500) c.Divide(2,1) hist.SetMinimum(1) hist.SetMaximum(V**2) hist2.SetMinimum(1) hist2.SetMaximum(V**2) hist.SetContour(50) hist2.SetContour(50) #hist.GetZaxis().SetTitle(" |F|^{2} ") #hist2.GetZaxis().SetTitle(" |F|^{2} ") c.cd(1) ROOT.gPad.SetLogz() hist.GetXaxis().SetTitle(" q_{y} [nm^{-1}] ") hist.GetXaxis().CenterTitle() hist.GetXaxis().SetTitleOffset(1.1) hist.GetYaxis().SetTitle(" q_{z} [nm^{-1}] ") hist.GetYaxis().CenterTitle() hist.GetYaxis().SetTitleOffset(1.4) hist.GetZaxis().SetTitleOffset(1.3) hist.Draw("col") #t.SetNDC(1) #t.SetTextSize(8.0e-2) #t.DrawText(0.1, 0.01, "a") c.cd(2) ROOT.gPad.SetLogz() hist2.SetMinimum(1) hist2.GetXaxis().SetTitle(" q_{x} [nm^{-1}] ") hist2.GetXaxis().CenterTitle() hist2.GetXaxis().SetTitleOffset(1.1) hist2.GetYaxis().SetTitle(" q_{y} [nm^{-1}] ") hist2.GetYaxis().CenterTitle() hist2.GetYaxis().SetTitleOffset(1.4) hist2.GetZaxis().SetTitleOffset(1.3) hist2.Draw("col") #t.DrawText(0.1, 0.004, "b") #c.cd(3) #ROOT.gPad.SetLogy() #py = hist.ProjectionX("py", fbin, lbin, 'o') #py.GetYaxis().SetTitle(" |F|^{2} ") #py.GetYaxis().SetTitleOffset(1.3) #py.Draw() #t.DrawText(0.1, 0.01, "c") #c.cd(4) #ROOT.gPad.SetLogy() #px = hist2.ProjectionX("px", fbin, lbin, 'o') #px.GetYaxis().SetTitle(" |F|^{2} ") #px.GetYaxis().SetTitleOffset(1.3) #px.Draw() #t.DrawText(0.1, 0.004, "d") c.Update() ROOT.gApplication.Run() libformfactor-v0.3.2-7e235d29b1ab60f2e4788e7c819d7c0277aa2954/devtools/plot/plotpardep.py000066400000000000000000000103521477075301700274230ustar00rootroot00000000000000# script to plot the form factor # after the ROOT window with plots appear, # just save the picture in that format which you need import ROOT from libBornAgainCore import * # define a form factor, I recommend a 10--20 nm diameter #ff = FormFactorFullSphere(10.0*nanometer) ff05 = FormFactorSphere(10.0*nanometer, 5.0*nanometer) ff10 = FormFactorSphere(10.0*nanometer, 10.0*nanometer) ff15 = FormFactorSphere(10.0*nanometer, 15.0*nanometer) # zero q vector zero = cvector_t(0,0,0) # volume of particles V05 = abs(ff05.evaluate_for_q(zero)) V10 = abs(ff10.evaluate_for_q(zero)) V15 = abs(ff15.evaluate_for_q(zero)) # number of bins for qx, qy, qz # I recommend to put at least 400 for a better picture quality nqy = 100 nqz = 100 nqx = 100 # minimum and maximum values for qx, qy qz qymin = -2.0 qymax = 2.0 qzmin = -2.0 qzmax = 2.0 qxmin = -2.0 qxmax = 2.0 # first and last bin for the projection slice # if number of bins=400, then fbin should be set to 200 and lbin to 201 #fbin=50 #lbin=51 # step for qx, qy, qz stepqy = (qymax - qymin)/(nqy-1) stepqz = (qzmax - qzmin)/(nqz-1) stepqx = (qxmax - qxmin)/(nqx-1) # define style of the diagram ROOT.gROOT.SetStyle("Plain") ROOT.gStyle.SetOptStat(0); ROOT.gStyle.SetOptTitle(1); ROOT.gStyle.SetLabelSize(0.06, "xyz"); ROOT.gStyle.SetTitleSize(0.06, "xyz"); ROOT.gStyle.SetTitleFontSize(0.1); #ROOT.gStyle.SetPadRightMargin(0.19) ROOT.gStyle.SetPadLeftMargin(0.18) ROOT.gStyle.SetPadBottomMargin(0.18) ROOT.gStyle.SetTitleX(0.3); #ROOT.gStyle.SetTitleW(0.5); #t = ROOT.TText() # create ROOT histograms h05 = ROOT.TH2D("h05nm","H = 5 nm",nqy,qymin,qymax, nqz, qzmin, qzmax) h10 = ROOT.TH2D("h10nm","H = 10 nm",nqy,qymin,qymax, nqz, qzmin, qzmax) h15 = ROOT.TH2D("h15nm","H = 15 nm",nqy,qymin,qymax, nqz, qzmin, qzmax) # and fill them with the values for i in range(nqy): qy = qymin + i*stepqy for j in range(nqz): qz = qzmin + j*stepqz k = cvector_t(0,qy,qz) h05.Fill(qy,qz,(abs(ff05.evaluate_for_q(k))/V05)**2) h10.Fill(qy,qz,(abs(ff10.evaluate_for_q(k))/V10)**2) h15.Fill(qy,qz,(abs(ff15.evaluate_for_q(k))/V15)**2) # create a ROOT canvas and put all plots on it c = ROOT.TCanvas("FormfactorSphere","Formfactor Sphere", 1200,400) c.Divide(3,1) h05.SetMinimum(5e-8) #h05.SetMaximum(V05**2) h05.SetMaximum(1) h10.SetMinimum(5e-8) #h10.SetMaximum(V10**2) h10.SetMaximum(1) h15.SetMinimum(5e-8) #h15.SetMaximum(V15**2) h15.SetMaximum(1) h05.SetContour(50) h10.SetContour(50) h15.SetContour(50) #h05.GetZaxis().SetTitle(" |F|^{2} ") #h10.GetZaxis().SetTitle(" |F|^{2} ") #h15.GetZaxis().SetTitle(" |F|^{2} ") c.cd(1) ROOT.gPad.SetLogz() h05.GetXaxis().SetTitle(" q_{y} [nm^{-1}] ") h05.GetXaxis().CenterTitle() h05.GetXaxis().SetTitleOffset(1.1) h05.GetYaxis().SetTitle(" q_{z} [nm^{-1}] ") h05.GetYaxis().CenterTitle() h05.GetYaxis().SetTitleOffset(1.4) h05.GetZaxis().SetTitleOffset(1.3) h05.Draw("col") #t.SetNDC(1) #t.SetTextSize(8.0e-2) #t.DrawText(0.1, 0.01, "H = 5 nm") c.cd(2) ROOT.gPad.SetLogz() h10.GetXaxis().SetTitle(" q_{y} [nm^{-1}] ") h10.GetXaxis().CenterTitle() h10.GetXaxis().SetTitleOffset(1.1) h10.GetYaxis().SetTitle(" q_{z} [nm^{-1}] ") h10.GetYaxis().CenterTitle() h10.GetYaxis().SetTitleOffset(1.4) h10.GetZaxis().SetTitleOffset(1.3) h10.Draw("col") #t.SetNDC(1) #t.SetTextSize(8.0e-2) #t.DrawText(0.1, 0.01, "H = 10 nm") c.cd(3) ROOT.gPad.SetLogz() h15.GetXaxis().SetTitle(" q_{y} [nm^{-1}] ") h15.GetXaxis().CenterTitle() h15.GetXaxis().SetTitleOffset(1.1) h15.GetYaxis().SetTitle(" q_{z} [nm^{-1}] ") h15.GetYaxis().CenterTitle() h15.GetYaxis().SetTitleOffset(1.4) h15.GetZaxis().SetTitleOffset(1.3) h15.Draw("col") #t.SetNDC(1) #t.SetTextSize(8.0e-2) #t.DrawText(0.1, 0.01, "H = 15 nm") # draw projections #c.cd(4) #ROOT.gPad.SetLogy() #p05 = h05.ProjectionX("p05", fbin, lbin, 'o') #p05.GetYaxis().SetTitle(" |F|^{2} ") #p05.GetYaxis().SetTitleOffset(1.3) #p05.Draw() #t.DrawText(0.1, 0.01, "d") #c.cd(5) #ROOT.gPad.SetLogy() #p10 = h10.ProjectionX("p10", fbin, lbin, 'o') #p10.GetYaxis().SetTitle(" |F|^{2} ") #p10.GetYaxis().SetTitleOffset(1.3) #p10.Draw() #t.DrawText(0.1, 0.01, "e") #c.cd(6) #ROOT.gPad.SetLogy() #p15 = h15.ProjectionX("p15", fbin, lbin, 'o') #p15.GetYaxis().SetTitle(" |F|^{2} ") #p15.GetYaxis().SetTitleOffset(1.3) #p15.Draw() #t.DrawText(0.1, 0.01, "f") c.Update() ROOT.gApplication.Run() libformfactor-v0.3.2-7e235d29b1ab60f2e4788e7c819d7c0277aa2954/devtools/plot/plotpardeppyramid.py000066400000000000000000000112611477075301700310110ustar00rootroot00000000000000# script to plot the form factor # after the ROOT window with plots appear, # just save the picture in that format which you need import ROOT from libBornAgainCore import * # define a form factor, I recommend a 10--20 nm diameter # vary H ff05 = FormFactorPyramid(2.5*nanometer, 5.0*nanometer, 60*degree) ff10 = FormFactorPyramid(5.0*nanometer, 5.0*nanometer, 60*degree) ff15 = FormFactorPyramid(7.5*nanometer, 5.0*nanometer, 60*degree) # vary alpha #ff05 = FormFactorPyramid(5.0*nanometer, 5.0*nanometer, 50*degree) #ff10 = FormFactorPyramid(5.0*nanometer, 5.0*nanometer, 65*degree) #ff15 = FormFactorPyramid(5.0*nanometer, 5.0*nanometer, 80*degree) # zero q vector zero = cvector_t(0,0,0) # volume of particles V05 = abs(ff05.evaluate_for_q(zero)) V10 = abs(ff10.evaluate_for_q(zero)) V15 = abs(ff15.evaluate_for_q(zero)) # number of bins for qx, qy, qz # I recommend to put at least 400 for a better picture quality nqy = 400 nqz = 400 nqx = 400 # minimum and maximum values for qx, qy qz qymin = -2.0 qymax = 2.0 qzmin = -2.0 qzmax = 2.0 qxmin = -2.0 qxmax = 2.0 # first and last bin for the projection slice # if number of bins=400, then fbin should be set to 200 and lbin to 201 #fbin=50 #lbin=51 # step for qx, qy, qz stepqy = (qymax - qymin)/(nqy-1) stepqz = (qzmax - qzmin)/(nqz-1) stepqx = (qxmax - qxmin)/(nqx-1) # define style of the diagram ROOT.gROOT.SetStyle("Plain") ROOT.gStyle.SetOptStat(0); ROOT.gStyle.SetOptTitle(1); ROOT.gStyle.SetLabelSize(0.06, "xyz"); ROOT.gStyle.SetTitleSize(0.06, "xyz"); ROOT.gStyle.SetTitleFontSize(0.1); #ROOT.gStyle.SetPadRightMargin(0.19) ROOT.gStyle.SetPadLeftMargin(0.18) ROOT.gStyle.SetPadBottomMargin(0.18) ROOT.gStyle.SetTitleX(0.3); #ROOT.gStyle.SetTitleW(0.5); #t = ROOT.TText() # create ROOT histograms h05 = ROOT.TH2D("h05nm","H = 2.5 nm",nqy,qymin,qymax, nqz, qzmin, qzmax) h10 = ROOT.TH2D("h10nm","H = 5.0 nm",nqy,qymin,qymax, nqz, qzmin, qzmax) h15 = ROOT.TH2D("h15nm","H = 7.5 nm",nqy,qymin,qymax, nqz, qzmin, qzmax) #h05 = ROOT.TH2D("a50"," #alpha = 50^{o} ",nqy,qymin,qymax, nqz, qzmin, qzmax) #h10 = ROOT.TH2D("a65"," #alpha = 65^{o} ",nqy,qymin,qymax, nqz, qzmin, qzmax) #h15 = ROOT.TH2D("a80"," #alpha = 80^{o} ",nqy,qymin,qymax, nqz, qzmin, qzmax) # and fill them with the values for i in range(nqy): qy = qymin + i*stepqy for j in range(nqz): qz = qzmin + j*stepqz k = cvector_t(0,qy,qz) h05.Fill(qy,qz,(abs(ff05.evaluate_for_q(k))/V05)**2) h10.Fill(qy,qz,(abs(ff10.evaluate_for_q(k))/V10)**2) h15.Fill(qy,qz,(abs(ff15.evaluate_for_q(k))/V15)**2) # create a ROOT canvas and put all plots on it c = ROOT.TCanvas("FormfactorSphere","Formfactor Sphere", 1200,400) c.Divide(3,1) h05.SetMinimum(1e-7) #h05.SetMaximum(V05**2) h05.SetMaximum(1) h10.SetMinimum(1e-7) #h10.SetMaximum(V10**2) h10.SetMaximum(1) h15.SetMinimum(1e-7) #h15.SetMaximum(V15**2) h15.SetMaximum(1) h05.SetContour(50) h10.SetContour(50) h15.SetContour(50) #h05.GetZaxis().SetTitle(" |F|^{2} ") #h10.GetZaxis().SetTitle(" |F|^{2} ") #h15.GetZaxis().SetTitle(" |F|^{2} ") c.cd(1) ROOT.gPad.SetLogz() h05.GetXaxis().SetTitle(" q_{y} [nm^{-1}] ") h05.GetXaxis().CenterTitle() h05.GetXaxis().SetTitleOffset(1.1) h05.GetYaxis().SetTitle(" q_{z} [nm^{-1}] ") h05.GetYaxis().CenterTitle() h05.GetYaxis().SetTitleOffset(1.4) h05.GetZaxis().SetTitleOffset(1.3) h05.Draw("col") #t.SetNDC(1) #t.SetTextSize(8.0e-2) #t.DrawText(0.1, 0.01, "H = 5 nm") c.cd(2) ROOT.gPad.SetLogz() h10.GetXaxis().SetTitle(" q_{y} [nm^{-1}] ") h10.GetXaxis().CenterTitle() h10.GetXaxis().SetTitleOffset(1.1) h10.GetYaxis().SetTitle(" q_{z} [nm^{-1}] ") h10.GetYaxis().CenterTitle() h10.GetYaxis().SetTitleOffset(1.4) h10.GetZaxis().SetTitleOffset(1.3) h10.Draw("col") #t.SetNDC(1) #t.SetTextSize(8.0e-2) #t.DrawText(0.1, 0.01, "H = 10 nm") c.cd(3) ROOT.gPad.SetLogz() h15.GetXaxis().SetTitle(" q_{y} [nm^{-1}] ") h15.GetXaxis().CenterTitle() h15.GetXaxis().SetTitleOffset(1.1) h15.GetYaxis().SetTitle(" q_{z} [nm^{-1}] ") h15.GetYaxis().CenterTitle() h15.GetYaxis().SetTitleOffset(1.4) h15.GetZaxis().SetTitleOffset(1.3) h15.Draw("col") #t.SetNDC(1) #t.SetTextSize(8.0e-2) #t.DrawText(0.1, 0.01, "H = 15 nm") # draw projections #c.cd(4) #ROOT.gPad.SetLogy() #p05 = h05.ProjectionX("p05", fbin, lbin, 'o') #p05.GetYaxis().SetTitle(" |F|^{2} ") #p05.GetYaxis().SetTitleOffset(1.3) #p05.Draw() #t.DrawText(0.1, 0.01, "d") #c.cd(5) #ROOT.gPad.SetLogy() #p10 = h10.ProjectionX("p10", fbin, lbin, 'o') #p10.GetYaxis().SetTitle(" |F|^{2} ") #p10.GetYaxis().SetTitleOffset(1.3) #p10.Draw() #t.DrawText(0.1, 0.01, "e") #c.cd(6) #ROOT.gPad.SetLogy() #p15 = h15.ProjectionX("p15", fbin, lbin, 'o') #p15.GetYaxis().SetTitle(" |F|^{2} ") #p15.GetYaxis().SetTitleOffset(1.3) #p15.Draw() #t.DrawText(0.1, 0.01, "f") c.Update() ROOT.gApplication.Run() libformfactor-v0.3.2-7e235d29b1ab60f2e4788e7c819d7c0277aa2954/devtools/polyhedron/000077500000000000000000000000001477075301700261035ustar00rootroot00000000000000setup_dodecahedron.py000077500000000000000000000036471477075301700322520ustar00rootroot00000000000000libformfactor-v0.3.2-7e235d29b1ab60f2e4788e7c819d7c0277aa2954/devtools/polyhedron#!/usr/bin/env python3 # setup_dodecahedron.py: # Compute vertex coordinates of a regular dodecahedron. # Auxiliary program, used during implementation of dodecahedron form factor in BornAgain. # Joachim Wuttke, Forschungszentrum Jülich, 2016 from math import sqrt,sin,cos,pi def distance(s,t): return sqrt((s[0]-t[0])**2+(s[1]-t[1])**2+(s[2]-t[2])**2) q=sqrt(5) # v=pi/5 # vv=2*v c=(1+q)/4 # cos(v) s=sqrt((5-q)/8) # sin(v) cc=(q-1)/4 # cos(vv) ss=sqrt((5+q)/8) # sin(vv) def plane_rotate(k): t=p[k] ci = cc si = ss x = ci*t[0]-si*t[1] y = si*t[0]+ci*t[1] p[k+1] = [x, y,t[2]] p[k+4] = [x,-y,t[2]] ci = -c si = s x = ci*t[0]-si*t[1] y = si*t[0]+ci*t[1] p[k+2] = [x, y,t[2]] p[k+3] = [x,-y,t[2]] # a=pow((7*q-15)/5, 1/3.) # edge for V=1 a=1. rb=a/(2*s) rd=a*c/s R2a=(9+3*q)/8 b=a*sqrt(R2a-1/(4*s**2)) d=a*sqrt(R2a-(c/s)**2) h=rb*c vol = 12 * 5 * h*a*b/6 print("a=%g, R/a=%g, rb/a=%g, rd/a=%g, b/a=%g, d/a=%g" % (a, sqrt(R2a), rb/a, rd/a, b/a, d/a)) print("h=%g volume=%20.16g" % (h, vol) ) p=[None for i in range(20)] p[ 0] = [+rb,0.,-b] p[ 5] = [+rd,0.,-d] p[10] = [-rd,0.,+d] p[15] = [-rb,0.,+b] for m in range(4): plane_rotate(m*5) for i in range(len(p)): print(" {%20.16g*a,%20.16g*a,%20.16g*a}," % (p[i][0], p[i][1], p[i][2])) for i in range(20): print("p%2i(%20.16g,%20.16g,%20.16g) at R=%20.16g" % (i, p[i][0], p[i][1], p[i][2], distance(p[i],[0,0,0]))) distcoll = {} for i in range(20): for j in range(i+1,20): dist = distance(p[i],p[j]) print("p%2i(%9.4g,%9.4g,%9.4g) - p%2i(%9.4g,%9.4g,%9.4g) = %20.17g" % (i, p[i][0], p[i][1], p[i][2], j, p[j][0], p[j][1], p[j][2], dist)) dist=round(dist*1e14)/10**14 if not dist in distcoll: distcoll[dist] = 1 else: distcoll[dist] += 1 for dist in sorted(distcoll.keys()): print( "%2i times %20.16g" % (distcoll[dist],dist) ) setup_icosahedron.py000077500000000000000000000037261477075301700321270ustar00rootroot00000000000000libformfactor-v0.3.2-7e235d29b1ab60f2e4788e7c819d7c0277aa2954/devtools/polyhedron#!/usr/bin/env python3 # setup_icosahedron.py: # Compute vertex coordinates of a regular icosahedron. # Auxiliary program, used during implementation of icosahedron form factor in BornAgain. # Joachim Wuttke, Forschungszentrum Jülich, 2016 from math import sqrt,sin,cos,pi def distance(s,t): return sqrt((s[0]-t[0])**2+(s[1]-t[1])**2+(s[2]-t[2])**2) c=sqrt(3)/2 q=sqrt(5) ss=c cc=.5 # a=pow(3*(3-q)/5, 1/3.) # edge for V=1 a=1. R2=(5+q)/8*a**2 rb=a/sqrt(3) b=a*sqrt((7+3*q)/24) # sqrt(R2-rb**2) rd2=a**2*(3+q)/6 # (4*R2-a**2)/2/(1+cc) rd=sqrt(rd2) d=a*sqrt((3-q)/24) #sqrt(R2-rd2) h=rb+sqrt(rb**2-(0.5*a)**2) vol = 20 * h*a*b/6 print("a=%g, R=%g, rb=%g, rd=%g, b=%g, d=%g" % (a, sqrt(R2), rb, rd, b, d)) print("check P14=%g" % (sqrt( (rd*cc-rb)**2 + (rd*ss)**2 + (d-b)**2 ))) print("h=%g volume=%20.16g" % (h, vol) ) p=[None for i in range(12)] p[ 0] = [ rb, 0, -b] p[ 1] = [-rb*cos(pi/3),+rb*sin(pi/3),-b] p[ 2] = [-rb*cos(pi/3),-rb*sin(pi/3),-b] p[ 3] = [-rd, 0, -d] p[ 4] = [ rd*cos(pi/3),+rd*sin(pi/3),-d] p[ 5] = [ rd*cos(pi/3),-rd*sin(pi/3),-d] p[ 6] = [ rd, 0, +d] p[ 7] = [-rd*cos(pi/3),+rd*sin(pi/3),+d] p[ 8] = [-rd*cos(pi/3),-rd*sin(pi/3),+d] p[ 9] = [-rb, 0, +b] p[10] = [ rb*cos(pi/3),+rb*sin(pi/3),+b] p[11] = [ rb*cos(pi/3),-rb*sin(pi/3),+b] for i in range(len(p)): print(" {%20.16g*a,%20.16g*a,%20.16g*a}," % (p[i][0], p[i][1], p[i][2])) for i in range(len(p)): print("p%2i(%20.16g,%20.16g,%20.16g) at R=%20.16g" % (i, p[i][0], p[i][1], p[i][2], distance(p[i],[0,0,0]))) distcoll = {} for i in range(len(p)): for j in range(i+1,len(p)): dist = distance(p[i],p[j]) print("p%2i-%2i = %20.17g" % (i, j, dist)) dist=round(dist*1e15)/10**15 if not dist in distcoll: distcoll[dist] = 1 else: distcoll[dist] += 1 for dist in sorted(distcoll.keys()): print( "%2i times %20.16g" % (distcoll[dist],dist) ) libformfactor-v0.3.2-7e235d29b1ab60f2e4788e7c819d7c0277aa2954/devtools/test/000077500000000000000000000000001477075301700246775ustar00rootroot00000000000000libformfactor-v0.3.2-7e235d29b1ab60f2e4788e7c819d7c0277aa2954/devtools/test/Makefile000066400000000000000000000010251477075301700263350ustar00rootroot00000000000000CC = g++ -ggdb -O3 -std=c++11 -Wall -I/G/ba/Core/FormFactors/ -I/G/ba/Core/Geometry -I/G/ba/Core/Tools -I/G/ba/Core/Samples -I/usr/include/eigen3 # LIBS= -lm -lgslcblas -lgsl -lyaml-cpp BIND = ./ all: $(BIND)runff # source from BornAgain overwrites -lBornAgainCore, cf http://stackoverflow.com/questions/36667429 SRC = runff.cpp /G/ba/Core/FormFactors/FormFactorPolyhedron.cpp DIAG = -D POLYHEDRAL_DIAGNOSTIC=ON $(BIND)runff: $(SRC) $(CC) $(DIAG) -L../../../build/lib/ -l:_libBornAgainCore.so -o $(BIND)runff $(SRC) libformfactor-v0.3.2-7e235d29b1ab60f2e4788e7c819d7c0277aa2954/devtools/test/ff_diff.py000077500000000000000000000025171477075301700266440ustar00rootroot00000000000000#!/usr/bin/env python3 import re, sys from math import sqrt def read_file( fn ): fd = open( fn, 'r' ) ti = fd.read().rstrip() fd.close ti = re.sub( r'^\s*#.*\n', '', ti, re.M ) return ti.split("\n") ## Main maxdiff = 0 fpair = sys.argv[1:3] tpair = [ read_file( fn ) for fn in fpair ] if len(tpair[0])!=len(tpair[1]): raise Exception( "tables differ in size" ) for i in range(len(tpair[0])): dpair = [ re.split(r'\s+', t[i]) for t in tpair] if len(dpair[0])!=len(dpair[1]): raise Exception( "lines %i differ in size" % i ) for j in range(5): if dpair[0][j]!=dpair[1][j]: raise Exception( "lines %i differ in parameter %i" % (i,j) ) xypair = [ [ float(d[8]), float(d[9]) ] for d in dpair ] out_of_range = False for x,y in xypair: if abs(x)>1e166 or abs(y)>1e166: out_of_range = True break if out_of_range: print( tpair[0][i] ) print( tpair[1][i] ) print( "result too large" ) continue apair = [ sqrt(x**2+y**2) for x,y in xypair ] aval = (apair[0] + apair[1])/2 diff = max( abs(xypair[0][0]-xypair[1][0]), abs(xypair[0][1]-xypair[1][1]) ) / aval maxdiff = max( maxdiff, diff ) if diff>3.e-12: print( " ".join(dpair[0]+dpair[1][8:]+["%g" % diff]) ) print( "# maxdiff = %g" % maxdiff ) libformfactor-v0.3.2-7e235d29b1ab60f2e4788e7c819d7c0277aa2954/devtools/test/limit-run000066400000000000000000000015341477075301700265450ustar00rootroot00000000000000# no q expansion, hence sum over faces, then no expansion in qpa runff 2 7 4 1e-99 1e-99 0 > x0 # no q expansion, hence sum over faces, then expand in qpa runff 2 7 4 1e-99 1e99 2 > a1 runff 2 7 4 1e-99 1e99 4 > a2 runff 2 7 4 1e-99 1e99 6 > a3 runff 2 7 4 1e-99 1e99 8 > a4 runff 2 7 4 1e-99 1e99 10 > a5 runff 2 7 4 1e-99 1e99 12 > a6 runff 2 7 4 1e-99 1e99 14 > a7 runff 2 7 4 1e-99 1e99 16 > a8 runff 2 7 4 1e-99 1e99 18 > a9 runff 2 7 4 1e-99 1e99 20 > as # q expansion (no need for qpa expansion) runff 2 7 4 1e99 1e-99 2 > h1 runff 2 7 4 1e99 1e-99 4 > h2 runff 2 7 4 1e99 1e-99 6 > h3 runff 2 7 4 1e99 1e-99 8 > h4 runff 2 7 4 1e99 1e-99 10 > h5 runff 2 7 4 1e99 1e-99 12 > h6 runff 2 7 4 1e99 1e-99 14 > h7 runff 2 7 4 1e99 1e-99 16 > h8 runff 2 7 4 1e99 1e-99 18 > h9 runff 2 7 4 1e99 1e-99 20 > hs libformfactor-v0.3.2-7e235d29b1ab60f2e4788e7c819d7c0277aa2954/devtools/test/runff.cpp000066400000000000000000000330441477075301700265270ustar00rootroot00000000000000// Form factor computation: test program // JWu 2016 // Reference: // Joachim Wuttke // "Form factor (Fourier shape transform) of polygon and polyhedron." // To use these tests, FormFactorPolyhedron.cpp must be compiled // with flag -D POLYHEDRAL_DIAGNOSTIC=ON #include #include #include #include #include #include "ParticleShapes.h" #include "FormFactorTriangle.h" using std::cout; using std::cerr; using std::vector; static complex_t I(0.,1.); static double eps(2e-16); Diagnosis diagnosis; int nshape = 13; extern int n_limit; extern double q_limit_series; extern double qpa_limit_series; //! Returns a pointer to a particle, according to given code IFormFactorBorn* make_particle( int ishape ) { if ( ishape==1 ) { return new FormFactorDodecahedron(3.); } else if( ishape==2 ) { return new FormFactorIcosahedron(15.); } else if( ishape==3 ) { // true tetrahedron double alpha = 72 * Units::degree; return new FormFactorTetrahedron(1., tan(alpha)/2/sqrt(3), alpha); } else if( ishape==4 ) { // tetrahedral frustum double alpha = 72 * Units::degree; return new FormFactorTetrahedron(1., 0.5*tan(alpha)/2/sqrt(3), alpha); } else if( ishape==5 ) { // tetrahedral frustum, flat one double alpha = 80 * Units::degree; return new FormFactorTetrahedron(1., 0.1*tan(alpha)/2/sqrt(3), alpha); } else if( ishape==6 ) { // tetrahedral frustum as in BasicTest return new FormFactorTetrahedron(16., 4., .8); } else if( ishape==7 ) { double alpha = 72 * Units::degree; return new FormFactorCone6(10., 10., alpha); } else if( ishape==8 ) { return new FormFactorPyramid(1.5, .24, 1.); } else if( ishape==9 ) { return new FormFactorAnisoPyramid(2, .4, .24, 1.); } else if( ishape==10) { return new FormFactorPrism3(1.2, 1.); } else if( ishape==11) { return new FormFactorPrism6(1., 1.); } else if( ishape==12) { return new FormFactorTruncatedCube(4., 1.); } else if( ishape==13 ) { double alpha = 73 * Units::degree; return new FormFactorCuboctahedron(1., 1., .8, alpha); } else if( ishape==90 ) { return new FormFactorTriangle(1.); } else throw "Shape not implemented"; } //! Print q in a form that can be easily pasted to the command line for further investigation std::string nice_q( cvector_t q ) { std::ostringstream ret; double qmax = 0; ret << std::setprecision(16); for( int i=0; i<3; ++i ) qmax = std::max( qmax, q[i].real() ); for( int i=0; i<3; ++i ) ret << q[i].real()/qmax << " " << q[i].imag()/qmax << " "; ret << qmax; return ret.str(); } //! Bisect between two q's to find possible discontinuities void bisect( const IFormFactorBorn* polyh, int ishape, double q0mag, cvector_t qi, complex_t ri, const Diagnosis di, cvector_t qf, complex_t rf, const Diagnosis df, double& maxrelstep ) { assert( di!=df ); if( (qi-qf).mag()<4*eps*q0mag ) { // narrowed down to minimal step, now check for continuity double aval = (std::abs(ri) + std::abs(rf))/2; double step = std::abs(ri-rf); double relstep = step/aval; if( relstep>maxrelstep){ cout<<"ishape="<"<evaluate_for_q(q2); Diagnosis d2 = diagnosis; if( d2!=di ) bisect( polyh, ishape, q0mag, qi, ri, di, q2, r2, d2, maxrelstep ); if( d2!=df ) bisect( polyh, ishape, q0mag, q2, r2, d2, qf, rf, df, maxrelstep ); } //! Computes form factor, and prints result according to outfilter. void run( const IFormFactorBorn* polyh, int ishape, cvector_t q, int outfilter ) { complex_t ret = polyh->evaluate_for_q(q); cout<evaluate_for_q( q ); } void test_matching( int ishape, const vector>& scans ) { cout<& q_scan: scans ) { assert( q_scan.size()== 1 ); cvector_t uq = q_scan[0]; const cvector_t q = mag * uq; complex_t ff[2]; ff[0] = ff_modified( q, polyh, false, false ); ff[1] = ff_modified( q, polyh, true, false ); double dev = std::abs(ff[0]-ff[1])*2/(std::abs(ff[0])+std::abs(ff[1])); res = std::max(res, dev ); if( 0 && dev>.1 ) cerr<getRadius()<<" "<>& scans ) { IFormFactorBorn* polyh( make_particle( ishape ) ); double maxrelstep = 0; for( size_t j=0; j& q_scan = scans[j]; for( size_t i=1; ievaluate_for_q(q_scan[i-1]); Diagnosis last_diag = diagnosis; complex_t ret = polyh->evaluate_for_q(q_scan[i]); Diagnosis diag = diagnosis; if( diag!=last_diag ) bisect( polyh, ishape, q_scan[i].mag(), q_scan[i-1], last_ret, last_diag, q_scan[i], ret, diag, maxrelstep ); } } fprintf( stderr, "\n" ); cout<<"shape "< max rel step = "<>& scans ) { IFormFactorBorn* polyh( make_particle( ishape ) ); for( size_t j=0; j& q_scan = scans[j]; for( const cvector_t q: q_scan ) { complex_t ret = polyh->evaluate_for_q(q); cout << ishape<<" " << std::setprecision(16) << q[0].real()<<" "<> create_scans( int mode ) { static int n_dir = 7; cvector_t dir[n_dir] = { { 1., 0., 0. }, { 0., 1., 0. }, { 0., 0., 1. }, { 0., 1., 1. }, { 1., 0., 1. }, { 1., 1., 0. }, { 1., 1., 1. } }; static int n_absdir = 8; // absorption component cvector_t absdir[n_absdir] = { { 0., 0., 0. }, { 1e-15*I, 0., 0. }, { 0., 1e-10*I, 0. }, { 0., 0., 1e-5*I }, { .1*I, 0., 0. }, { 0., .1*I, 0. }, { 0., 0., .1*I }, { .1*I, .1*I, .1*I } }; vector mag; if ( mode==0 ) { mag.resize(1); mag[0] = 1; } else if ( mode==1 ) { mag = vector( { 0, 1e-12, 1e-10, 1e-8, 1e-7, 1e-6, 1e-5, 1e-4, 1e-3, .01, .06, .2, .5, 1, 2, 5, 10, 20, 50, 100 } ); } else if (mode==2 ) { mag.resize(1001); mag[0] = 0.; for( size_t i=1; i> scans; for( int i=0; i scan; for( size_t m=0; m= argc ) help_and_exit() #define MULTIARG(n) if( (arg+=n)-argv >= argc ) help_and_exit() int main (int argc, const char *argv[]) { try { diagnosis.debmsg = 0; diagnosis.request_convergence = false; const char** arg = argv; NEXTARG; if ( !strncmp( *arg, "def", 3 ) ) { // do nothing } else { double q_limit = atof( *arg ); NEXTARG; double qpa_limit = atof( *arg ); NEXTARG; int n_limit = atoi( *arg ); FormFactorPolyhedron::setLimits( q_limit, n_limit ); PolyhedralFace::setLimits( qpa_limit, n_limit ); } NEXTARG; int inmode = atoi( *arg ); if( inmode==1 ) { NEXTARG; diagnosis.debmsg = atoi( *arg ); } if( inmode<=2 ) { NEXTARG; int outfilter = atoi( *arg ); NEXTARG; int ishape = atoi( *arg ); IFormFactorBorn* P( make_particle( ishape ) ); MULTIARG(6); cvector_t uq( complex_t( atof(*(arg-5)), atof(*(arg-4)) ), complex_t( atof(*(arg-3)), atof(*(arg-2)) ), complex_t( atof(*(arg-1)), atof(*(arg-0)) ) ); double mag; if( inmode==0 ) { // for use through Frida int nop; std::cin >> nop; while( std::cin >> mag ) run( P, ishape, mag*uq, outfilter ); } else if( inmode==1 ) { NEXTARG; mag = atof( *arg ); run( P, ishape, mag*uq, outfilter ); } else if( inmode==2 ) { int n_mag = 2001; double mag_i = 1e-20; double mag_f = 1e4; for( int i=1; i> scans = create_scans( 1 ); double totmaxrelstep = 0; if( ishapepar==0 ) { for( int ishape=1; ishape<=nshape; ++ishape ) totmaxrelstep = std::max( totmaxrelstep, test_continuity( ishape, scans ) ); cout<<"grand total max rel step = "<> scans = create_scans( 0 ); if( ishapepar==0 ) { for( int ishape=1; ishape<=nshape; ++ishape ) test_matching( ishape, scans ); } else test_matching( ishapepar, scans ); exit (0); } // it's a straight loop over q vector> scans = create_scans( 2 ); if( ishapepar==0 ) { for( int ishape=1; ishape<=nshape; ++ishape ) loop_one_shape( ishape, scans ); } else loop_one_shape( ishapepar, scans ); exit(0); } catch( const char* ex ) { cerr<<"F(q) failed: "<ЌҼ$Ab00]Rʫ;ˈȬX|yf<3#=ܟϫ-o~ @ @{7 @ @ ! p @ @ @uA @ @ @ @A@. @ @ @ @ @ @;w@ @ @[ @ @ @` @ @ @wk @ @ pQ @ @ @@n  @ @ @;  @ @ܭ @ @ }D] @ @ @5@ @ @v  @ @  @jn)b @ý9 @ @$pj  @}(4 @,`nG @`7iڞ}r[ @6^!@ P`KO @T/HKh @ ,ӝ-0_ @p@ @`~כ+k4 @Q@# @+0տ:vz'@ @`}  @\#iMT]  @| @ XH<:} @ po9 @,QԞv7?2P)E @0GFc @y// @ 8Rf  @!0wr+W @=P @HQ.icLY} @x+ <^$@ pQ;}! @#p?BU @,OnZ1 @ hB @`gQԞzvh @kk\K @Q~PP:] @xp @Nؖ:}e  @G܏P' @RԾ-mW @lp67"@ %0 :Ffnn~q. @p_M  @22m15  @% pKR? @HQ{f=]!s_* @+)˥ @rFQr}er @ @8F`s%~/r  @v/r @=o?}󭽑 @>^%@ %0Jsq~e^x @:Gʬr5 @ퟧʤ8XfyM]I @  @lHQ{i̤d3("@ RvW @Xߞ6ڮ pz  @ p_2 @틺;𢙰>uM Ё"" @)⟵Ͻ  @  @% |"=cfzҩ2܏)^  @GS)i @.P|ԾSjTo+ @ ^M͈ @`r0ӵ]V @ @,8DcdF9Tto @x8RJ @ )pN޽Vo_?w'TT=C%@8JdK P9i{,^j) @2enL P@L:._ N @pL34 @eF'sۜ-s: @+`{12 @y6y܃& @@o @>y߳GeY|L Њ#eZy @"j_|ci}k  @YAuG P@iEOI- @@Aa( @G |"s12?zƧ/s? @pLy51" @c.ڏVSeR~1~;&H @q;/ @<KW^Ps/B p\ow#@8] Eե#n?}~gC{?9 @*n{ ivM p@{Qx?fE'۟"~H(4 @ýz- @^*uo> @P @!2[9-ѱ/D+.gy3 @@0C @ @QΝ'}KIqǹ,%B= @x-`k @+m~.swɽ @WN p!2Gqr'PZx @\Gʔ[##@t" j?Ѕt)s^‘8W @ý @fi{ޑ׺8srM[@w͞ p@ڥ;xm9H P#eJ1 @7_]&l]uB pQ @p\= _Hz @D{U1& @@>\{~i0lvO?  @e^F Тn9Yl}C @ w @ q^ % sf @#;s} @( jUWW=L÷ƲNo/~)sΖI9oE @p?ڝ @ |lkNmlSǗsL(^@^|  P(jOY:qaH7A @4GʜFF @Fqb_S+gI @z;כy @@HQ=x,m:OflrE @p?[ @- f9w)siAuI&@4"`{#4  @qV>YO @/ p?  @ ]ھ8V8e C澬j"@8Vqy  @ Wt6p>}X}$dg2?_U2 @,`f:o$@( m>zje @6mլ @ |"[w uAvٓ0g @|-ח @{Q{K&gˤlg @pO8^@ھq{ ic^類aw @'$ @I E0JBrB`֫ @:pL'6M @jQ_}{>}u@L˜^ @;ܭ @i>s(WOu/r @N uChE8m@15^!#@%Hi6 @<Q{N'rv2sU @!vªS @@=Oy瞽Nt@ H@ྈE @>qL5~v2 @  @¨9ɓcӁ0&I~_vC(4Ph @%`^!@T)0mrnM @sz @Cd7`W tq~b`p! @GlQ @@pv3?',m_p̂% @-voQ @@kF@<$@K@ྗ~ @|"12WsṮa  @n; @NQ{u[2r&̒{a}8[&}u% @@vWP$C$@d |lkHQԜs/Ch@@@M N`K#׺;2" @:Gʬr5 @.Q.j|F{eqv @@vPes$@z8D i;1Ŕ   @Y @ W_(ؙCb$k1Տ  @;ܯqwW @q Up{90 @@zkg @!2qg:j\b}hܣ6 @4u @JXcMPbK(Ka4 @mϛ w-;=lّ}a; @ 0+ pe$ @2_(]C  @ 8Rf+  @ ڋ+"lV*i @Sp? @; Hwܷlߑ GNw]W:#@t!`{e6I @1QԞf2K2oT @r5 @rQ.j" O0F`֒! @jpLM2V @58X"4,KM ^@ǫ @RR.m/љ3}k @HT- @ 23?Mh[pN{Hu$݆qŰt5Ph @D;ܣ6 @D 9E_ @5UX  @R.m?B-cʆ=h @L)35 B`9;!jt12‘2QcI;< :Geɔ^?;2o+E @@u={'@(MQ9JϞ @ @fG\=c'@R; s_urWy~_xS5h;%ֹ8 h @D;ܣ6 N`kk#dQ&Eo"p@܇eҗ. @J|kXI $p#X|!i3c~>X<1|śƗwCS~'=2!s % @.;몗 @,|1P ‚M ڽ,RBkq5s: @I@n @^b;gFS}yTH>;TO 0@<$@t. p|>X!0;Fd9 HWH~-0ޑ;aY5:%@) :f @`@Lu^^6vU(0mwtf7HN{H$c<_^%@t"`{'6MZ ǥx>QV#L ڻ)eo _\}+ r @@6kBl IDATjV @`4.;1=*?=l (MJ~疩s'n @' Ov+ н(Ok#iZ]W?/ N-̈́ U[式 i] )r;}|K?|vy~iP0}{' @~;믡 @1ޭ"mOsmg)!Ų4_  @M ܛ. @@1ᝦ6O3h_P 910b%АGqm @@Wm @zQ6ߩC#/83zhk {&@# ~jm Б@ y u)KYc.m?&>ήP<0^._V4la ЎL @C M`Uv>>::.j(zb[7w捆Ӱ=k @zPes$@b[{>X_^Q6O7GKۗ˕/ _ aD2Mh^@|M.Wp=+?QԞ:/K͝'L @X.%@ Ĵ'ˋ5ǭk| }uҭ_aDS5lr @m @#rbawmkOZ2P.#@. pO>.Ik֗/}+ # #ڬӐG;m @@Vgh^ @C~ Keݑ2Â:8k~'=t=/Yl @4#;D @@{)Xˉ79w M s=rسh{LaqL,6 @^ڀ) @'ApQԞdo#e/eCL~'=ư*n1<@AQ25V͘  @3Fi{'Q%PȉlMZ2U  @;O+4n8XpΖ"@, pzN H)xڥP Q~Ó @ 8ý!"9L@b;̷W:1ָ  @@-vR)$@X_ L>eN"@* p/0EQhc{?z>ppy\4 ԈC&@X'Hu^&@ Hۏ6[!хy2:G%ӘpLD&@T$`{E2TQ@~h#s( ,`tHVko @llJ}_AmmݙH=P&@+`{10 8[= }{I pQX' m_QԞ3G 3pN{H$1s}" @ 8RfS= @|i{l]>UOMyL^iDe6 @pG^ #@vF#p̹F. pn @ wMQ}wU Ba Giܣ6 @XG[#@N^Ծ;pGH @@vYW"@JNھ;8j+F+Z&X*m @@2bT @@cdr אܐ N2\M}G[wE v7!qg0 @p|>" m߱1u5wdO_?C#ڼӰ=Ji @U!@N{12w_fN2=5EO2YT`&> n~@=I FΖ @Bp/A ޾Ki.gK]}l @`  Я==u?>p_KKםO!M= cCPzH8W2z9Jۇ=u';hm89d #"ш @ 8RTn7#@vxx]FsTNHM9eI  @`#e1.`{{(jO]Ž폞p =<x߳\}I{H#$p* p#eqwW\@ڞFi4jJ۷V&Qei8=VB p#eNv# 𣀴}RHSھA[D24  @%W @@wC>j$(bo{Ю\ -> #VJ {,6 @|H] (+cI(Mҋ܇7֝CL~'=ư*|= g| xH \V$@)X\E!m_տ  И}t @Ԋe P@i k9ӻmO٬Pe% /v"F\46 @\A C1JFmt+6ˤ:Xە` @: @S궷O(jOMp~ 7D_xkH;0F)tcCQ! @gK ,Prڞ(?m?uL^F%U``Ejԭ0XKm @ 9- @kBB/(m_]@-[͗Vkݝ,ϝos@w)j3 ŖaĢа= m @p?W лq ɐ*QR֖e?:xv{~N{0PՏf>insC @vO !KڞϖwO[1 maĥ׈} @zXϦddzJZÈ!s"@#W BN}S݌ @xcw @|&L3ϻx<55B~1gh0oI  @{ @_HcdFwpCxVq@2+h @@):F H>^mB m0ÈE1HoϦs%,8Hm @@vZ9&@ H۫+_qCLXE4nGVm @^$Cj8F敌  pB|&@ZpL5/8[@JW2oW&4pL,6 @ _|C= @{_ve1@9TP(jO/ fpOPwC$-|qP7TG @ 8Rf1  @}\HDd46 @eGʴ\]s#@lo"Kۧ&%?c{{16 8X4j7"@]_#@c#KG&HK0X5d @mp] @JBvDž1Y]ȞWOwCc~'=h0 @Uvr1 0/PH>?8FEiD5s&@s=p̰:Ff.̞L#!s @U߭ @@B- }8[&[~?)  @`;wa t$ m-m(4Ɂ{טFԢT>(MX( p_2 @Scd>^!<ͼ@G2m @N R7 Ї6Ͳ#< #. QC @SO - ^@ڞ|W L`Ihԧa{6 @KK" @i(jO&eqk)kKBl*C{H$VţOâ @j@uTF  @A2Cx!0J _ 2i{ =.m @@pL&@=ooOQ{-i׳~pK\ 4Vk8[&i @^ _x(y>Z@]@JFx׎&}\'5 @ G4QF @bFv콈G2?ѕ @ZpRIṕ&𣀍q)hAct}qZ )|]e C @:{u%3`8\ 6Ihs:q{JS?_=zj @U|SE N?7\ _#"܇Ckk#tN{0'ʢnveƆ~ԃ`G  @ ^ZE9m7jrŸ7uQ^4x=so㿩=ࣰ6 p@`]~A WW 4%rAFi>t~cm>}j @`} @M7⍢tSnw[z'p {ʸܧ#Y"֕ @  @Y]YQ.jU$M wmݛn&HGp+J(H=p}4G=0#a  @@W&KgIǙk]`<޷KI~i:`zkUa{(ͼ^Dل: @^M v8(m?9XtS% @Uso<{m73G @){S4X"ݶKp5]~> LNÈ4('s=ѯ"ʉ &@U ܫ. @X~(?O? oQ6OEwK+ zkg E @;lo?-~QԞ.mox ͦoe{~'=w߃a<=_#s"a5<" @X;܋- @1mwAq$@Jv 43A #(]#`ZXض>jh @m @S}yJ?V<^$@@/3ڃv=k]㦶/w8Z@~  @MH;#:^4F4zGk5,3%@^ZE?~(m~_3b?:HN{H$xI{8(އF~`%OCMErKW l?a!4)`{e5)t- m&OJ/65'È4.а=؞dmI{LP_` 2Ne >؞Xm/km ԑj {$+=i;P^~эp̗D. @jlUk( eОϓ8%߃aĕNn gˌW h" P  @qvi{6A%A# v!k#I[+(! @EG Ը}'9iEm  @ T]2SkC~v \ Ua2Hǘ^i XcͿ+Ln {_Q6O2Pj15" Z*e R %m U%69} ?B=4N{0aU<=[c7D#%C8ZOFkio @`*`ԤglK  е@]Gik' IDAT 8$V(۫׆=NK'}OyDq-żJkk\O ?LFھq:ʾz#@@2bKsG=(~?ߗ㻒f>I@ tTx>tox#V,]9mFI~X(`9dtFEkS%xHpU +0JۧQ{ RVq #hG>XˊڧLl%y8H@~n  @XZKۏ]z_#5T%@ G<ӻn߅>nj B| @{'6M4%)0QԞ `o{Sd:(W c%j/Ektiz(_J4) &jRhY HSKen% m-Zb_?h;4N{0WX>i~cQ>Z=l/X !K?m v}͎.m2m`nIQWn s2FkNJ_>Q%  P9z(|{(jOL͹bl, Q _2XmB// [. @@Ee] dRa2]~ztCmuN*d$X%s>?OCW/= @hg-G} MkW bі@uv\\4hD.emn5 7C`:oG@<$@@v\c#@  &i @H }d.X/rh |z>n=y[&@݊ ğ1:Lfؾy{JmuwmǗFA# h&sOя(_'qz(g) ˯ @wD9i(jOERͿT55qA# v!k#錶` 1FE%@{;44)x(6mόm=nr&P&wi "{2E&׉I P̺+0۞;O##Я@!n #.A4@l6.|8Z`ۭG{AtO@nKo@ `PQԞ,#C$@>}jf9i;P^M nZ|  ğ Lw)v[+mN{H$}lQn34k\[~4yC,}! @T~SY݌?R3(KF|{z pqM @b\\'@+F(v\ٙ 4"0BX>ny0 -?͟Ls="p@uOn+ #E#f)UFB@{Bߴ>Y 0p3 p@д=>'m{݊m=A츔hЈ]ڈ繏8mf6D~D# DN mC`}J3O;Q.jU$dщ(G Dem=6|fQ2eŨ У@a2{Yt`0E@^KFowA@p/*Dwۏ`}*<mOkb}f_-2|`;0Uh(K06 }C?~!U^E  Ĵﮘ(m?'jbIH!.}.%Z`U>!?&Ϣ P& m?ڍ.s{%/nzlӧ #(P#鐶V/KawG{Ae) p?S۽ @`F ~|QԞgoL6bv=OAG~i$`%1o>i~2XD`%!T?5 p_bG@ھ^ZhDFֆebOn qz&@`}+  @`4C @`}-/:>-GVLZPEs @@EsNV txL>\mC1P5d~ڛ.y(p M&MA~Iݔ@p/:FⷧG=i C.qbľx>Eg:t$74E(sWW2=! G M*nR@eQC\%`UW^li-8   Pi{i* H!(۵ {b?mz?Ι(O]zx/  Ђ@.Гd?FrhN@\I?'$XC`G}@Aa( )0Jml,)"i=FtA# h- X~Վ~yG{A_r0pwA]~x!݀@ٸhЈ]ڰ=`[EkC*=[WU/%@@}ŃQԞmo1(@@^@> XЪY`$5/cH@QM [ϯ;+Ty"<iЈi'??^!:-X`  !p1 8:pm(mnXu֢&A>6{Hw$-| {}T鼱7W` N6l(_д=E x!PHXd>4hDZiWc@\Չ ,?>{F0w 8R98:mhc{6 gmș7HF׆eb=f۩H>8N>gv}F @Ã=z;e3} @@}~z?mBt{2>#3Ph 0Xp0biЈڸϖI^Hqv 8R X @_;%e @;"b$`{K4\/ mF@;BR&ÈEA# v)kMIp3*k P@68!@`}K @`icdٻXSOlfζ7SJ!0OMy굯Pg^&9Èk(ۥ {6U{sU'u],m^FK`#e֊ 8Ffw @);(g[(f3K//8ZO6oou`2| gI~(^V(_F!maim ~y/6 7y:IG\  M! #V( {t>N UU\.&Ѐ#e() @$/<T !@*pL%f_~C^LR(O;?- @`$f䡴}B j($0`hЈ]ڰ=O{@Z賟 2P@~![ @hicd.u&!<a^eIJ {׎z!_qlo]jpL4~"*m#pb{{HBGS @ ;\nv[  ]12 IB #. 4@lW6lr%lVgS6Yi,' t" pЦIK;FfK  @߾[Zz! j\ P~8nc&@oڎy3YO669}64N{0aU<=(| n=<(U@^je׾* ýo#PHbqEРba{,d9f?"o^ˬ( p N12tFX& s_tUz!9d:=W zݍr+g(FB ^[w'Dd"ds? #Ѡb8}8[w}sL\"AdinGX;܋- @TUi~܌"@@}R~ҹN gKff:o$@@;o12m{63"p@!" #(- {vwyiFFpL52B+>mG mHQL #. 4@l[QCS WW2&p mW\KC'@Jv{ g?K-qx)0刽@uOf;F2:R<BVF\4hDndm"~Bj @)R5ͅ^ۣ6 @~#qpYX`v{" L nI 'm'EO@i> Q 'S^G*g60 J۝'s T! pLI#s88X߄ụHBXiЈmmQT>RHM*WiUq_pL90'0GN @-#C] ^gaC@v\C#@1c\w3  Ɏ #֞(ƌAܓg6 lo/4t  0#3F"@\*ϦfRT7']`e&m~$@!G4TLS!@@fo%@Iɺ j Q ;]i>]˯$OC[mo&,m(4 pO!v_| #:ӠbژmrOL6m67R¯x D@~  @vIsK @?#,0cYzzrÇм@L@i=.s&@`@Imj FFfFoyL5e'MD iqz&Иt T@ھTujh?=Jnt#`{76u)[~bK~QfN%@(;܏/mo\Г%ݎfA1P Q ]:i&2} @.;몗 @`}57 @GB~7"iЈڰ=[Ue<,aK0Nc @; )a @`վ  @' ~"[.PȯȖsa<%!Љ4 N i' p᩷v<B~7.hЈmm,Ұ=2iW!g?|´'qv*0JӽO- @} ˃>J+ܛ͕Ԅ[ EiC%?se:($83hЈ]ڈCڿmz\/ئ)˷;OZszFnio{co([02A# ĶTCH_޴Η 0(G@^N-Yil2YOW$ziZ@+V t:IgY N@@hT!seӞ |OG[3S #!P6>6>eD@ Z!qHjZǝ:&]WTn|k$[XUs"@Cd+ϷL[E{x^GIe v{!aUKFֆ=vZ&~72WW2&@xc{ɧd>%sxHK@^Wv?Yv @Uݗf7oNXzfF)(0bhЈmm}@ZClWQ._X/)2O?>LYw p@ +se@?aUhЈmmj߷j~WICsgglx̣Ӹ7g$\MT@>ng:T. /@GZNtc'>\)PH$bqРb(E=-Xy]L@6h dc{9iT2OO^tM2x~ץ7$ poB@i*ӍO~  p]ѕ(FS-mrǥY ?moF;)i P(mOQ}Xho默BJFmkFhW녬1vʩ3 /wc$<.I[ @zgz| \vU0:nlqa>~145k&$_<ј؂w5Re;0F_b=(v? %>/vIs_i ME ^ty V8(d Ѧ@cqE2+Y~/wcvӺ}of%m_}5okZ T C!@U Y"N^ 쵽&Wž'@ ܣ6_ޯ1`s;g'z e/W~((P^8Y@~2 Щcd /PcIz>NFmkP&T3X@U 8Q@5  ptcPh @{L$뚅x/t6/@r w L+wOmBX. p_nJ,KYu2jKgy)aD~4@l[jܣvј8!MQ!:%pR~7'@9cdmoj(s(4 !<~B}܂ ++XÝ~$@`}w @`$0ؾGWGKC('nÈA# ĶQ=ILψmc8b{qЏyw }F @OFQ{zoic2&@(n_72È4hDض6 ԈC&H }%O?sEG*| ?- M m-]2bKc` @76Zi~ͤkkz[znl߰=a}} @@G~ë1P Q j4dTKҗVqqsIa{55nX@o[ }!<}ݬ  P@<׹2ug @`} ,12M-֠ӈZ4hD2J>pdߤS pB|&@Vm:5㖹r-kDQ((ֆ=J5N`)wq=WT;C%ЪʚG L;5}`hڃ:Ս=Vݜ &12?f*s߶伋Sm W @ ܣ6^ L7g#O0'sof// @ qĢQ=4U!'@@GgߧnL@^Y L#y ?{ 8(DFmk)2,QJ@~7Pi 667"@Ji~4vLۧ?u<9n\i. |@m%|F Q Qࣽ*sO hsܩ} ~XU}P@aM@'#-mg_4fC# @}[Bl% | K.\@0}]L7wٟ: ߈x@^?M #P4@l[ j2Bj2mb n` ЉB&~iq{ok٣R29Cx&!@ 0Oj(S@,^f]@f,8ǘFi譀- 4&PVHÈ((ۅ8$i+ + lO Z@ྚB`cd A%Ш@!AaEFmkM c]W ,IWub/ p? 8\`yhBCx?2$wQ'yan] '0=F洴ha(CvX4@l[4@l6d0; TW4 +C@f..mofmH[7O  @R~Wu'ۅ @`_; IۏNBU&Ө݈'S%wH#Ѡb8J>(Jڴ>Wu<ܼ.>?=SŨ  @@26jV 0' pSL.m?|} @@q<_iDg4@l[jܣ pvƇZh }/I p@ HO-f2 '+p: V ' p?V%0~12GML+d+\^@gg+ Q Q ]6"uN+rpUqM}j Kا7-$s, 3(4oFA# Ķq=7N_ELԟ{ƏD@'N mZ{m[ko= 9 X$f=H %dž;-%1ssU{Y[c kꮚսT22GldĹbB0 @,ԤvFfڑ#B"uWʈȞ^}1_ȸ6RI%V{mjy Jk )GZ^I!)|`}Ɏ/kYnQgPE)!`3ܭg ;Ʌesb @ @uÙ<>JR~|m^'A & wvb쿏+iD~'k?_%P04-h@CeƆD>6r߭Cʳ~@u 6@&jǶ;c]8JV&}ه44]fl@Cec֨,ݨ9 ;cH22m8Pb¹Qa(X}X4 @= +6.ֳXiEFV$Tt m $ljIނj:Ź7Tu%"<`0t M@(_+۶&Bpg@M1q9a/@S0C.h$w,f#xK^'A .#uIzdO>4 t~a2 O.&#-j-49x}{`474^>}vX[|y?R9%Vi*7啔 i/οr5Xy 4c_P-^e=mQ4Qگw EX' w">@m/Gm/g8O1@ P|O'cj `ZFk0Zi83L'`KiЀ&ˌ hh;6:w,X..ĪoTgj6t@ODۿ]C}azd"O>?U@:Jɤ7lXԸC.36Ѱepߚe@X}w$p#9m? qسmBǹ;H!!>!"|p3KxxN.' "h@Ce44]626tHe ~R"*ݑwjb&!pϡ1@6vSd[8>l[Jڪܙz\ @~z, gb3@F ܍$0 mo<I9G66zUwZ&mu%qA.36@satp)/E>l[rkrν ֈJ'  @s=wثZa ;; J@ GޢBiG6zyܽd8 `@z4A.36 򰱡V:8 px4MoT!|6@ =eX!ed6Lo]hu8W8w/"N_L. !B@H3{9O>!8Nl۾Yc~dSXGcJ C44]fl@CsOox /G>&V״sc6^82J7,U;D'4 2c.8w ;eєF,@yy8 8'k>̵UNsBo;S+ @ v,@Gw Pqbed"M4O HFV{h~4^ȅ044]fl@C墱$w2 $p7BKGnmS6 c7!ESO^:瑀̬" h@Ce44]<6pn;ێ>$pnXqbuќfpR :.>.@նG @Ȅ-B%S }@5dz{ ΝqГԋTҘ M@t$(,W{D}/c h(p E<4 2cc8wrkF 5 A v` ;Wsz걫d, @8#s?MB%9~L LLGEAh{JF o/ZZy:%Ѐ&ˌ hhظs׌2o9oöW+] ==#"YFfzEnG]@*܏C(֍c[08B+Uj@;&c۽I⟌EwFE`ᙚ0@44]flC<9 _n4a2>MC fWH%M;D?DZ9*Cc44]fl@V IDATCeccI[')|jW+=cp'p_| LꉤDZe+p{} ۿ] qUd*]:y<}7:νX @Uܫ`Lċ\g*Z@&L@hѾIl{{Ǝ[8C]#B$`o!ah&Ѐ&ˌ hh<`lc# @ =@ ugwfӪǹbE 0,g CS44]fl@CeccMcuv-U{A`4 &ۧ\F ;*5O@N K 5 v/rKk ܍' E`G ;Zu#s .? GÍ4 2c.>6Vs:Hgd@_A`b=vlqy<ϝwڈz @,Noo1&ZUE޳g1K+u6@3ϭڅ-+oDԪAOW2daᑓ044]fl@CeƆa<ֶ[b" IoV"22+e_3f{cw\,Cgt M@/sOrOq}n4*XXiۧ.ޞ9\&a揄_}C@۹My-u:ϣg+ 0)[S8sY>F{}E+6dz{j^!Z!0sV&"Ʉ44]fl@CeFeoogQk}#HHPL@>A]Ff+b|In3/DuJ  0wN88NE{C6ۗZF۾VjpP:"kfWd\ C\h@Ce44]ml䮳SFP*ȅ9 ܍'V$pؾ22[q^qXh8hThl 444?h@Ce44]56|9w;uFk[u/@ }h'pCw]N߇QpQ  t'Ź!@ ̎lԪucù?QU&4 2c.96;۾m2 Ѝ½jY.OjùצB}# Z}di Cs}~sGX}DMOhٶZu#prr/p^2EF.3 hh؀&ˌ+f{vZex@ >B`J)J8.##}EvyV8}(0R_$ˈ t M@it}Yl㿪ۛU!E+LJqy/*\8|oqt @@m׈g3 @)f?EhJM.bGLCyӤoGp77PЃt M@74 Nr/#ǃ$GJ>kf] KdlE#En+D@p@]Ԋ^>}YlAK^'AqbDKѰk,FXh%G hh0HCD!Q]κ]nҩZ5r*N*Z@"K88>l pAs2_at M@tFk.#U^D)H}5CXFY2vc; p)x'? @ @-~ ^ Z ,lj,#Ķg ( ΝA!'ahJЀ&ˌ hhHrݖNolۑA#&p;n$`bmLG>]JJt!Q ν^Ѷ/낗xH@ Wvc/@ @`Q>@<C9  ,.1a1 hh؀&ˌ Mr2 As *fy݌@22b1Z1hbyl N@OrSk[Z֫9H}ۙ$#n lb;ˑ١pm/DrYx4 2c.{{]۾ ^ 4"pojW$p+R@.y<}q@z2z1]|S{Ќ M@chpumbZ 6X&pbsCed"SHP67y=}$p,PE$l Z8w/OA ;@ 'g/#3" w pjYC!6}gUDZ2'Ѐ M@t9alTtէ/+ǗĔ!vR}nw۞Ό#w3E@ pKsno6k9:x#0 cֈ1@GV=avSPq]0҈܍m?4 2c.;6 ێ_Ý2 ,E۳)Low(L@O^ܽdj85,g hh؀&^Fsvʰ@ =@Ѷ?|͇GG|2D@N߆B K @9^ZE6@kzZȤBĶcNQ-9йK50r4 2c./=62&;e=)C+@}F8ed7wfV?йۅBdpFq@$ζ/+Ǘՠ;@6{m7)m54K8FJ0Mq&@ 9M CHh@Ce44]flTveruGA4HݖllQ;K8S\p7r/Ѐ&ˌ hhبFsojuˮ- bw/"ή-#ӵW4^!ݫ}9's?V@ 0ѹk k[٠T @{'4qb;ħ&Ԫ4]RFoЃt M@h""{GQ^[CLhD,z%p^{2"nl=ڔDzwi6ɿY+%@hXЀ&ˌ hhXF2rv %u;,A P۽[*l牑 H*FV\.r*啔 ))΍r5Xy Q i/[7^ehd+V1@~UՎS0(B8!О@`;p!>uUߗ>w:NJ@XR0!3] l(ޤo+Ŷw)zg7@3 m_"};)¼ 0|95##N044]fl@CFuîvreZu ` Fȉnpbb.5]!xA`)8vw @ 45}sO-~ZU[CӧB`>G9@NmDKjg[W%ܚ"NѲ c[iy $V^CTgd$IJەp?`XV>JY5!3] sݦjc&#`T( @`0g 1:E]pZ.M:FFe$$\MXoW!s_%#4F tabyaPv5]P.q-L(@K TB6S/@$njuF@K44]fl@CeFC$g\0[uf/@ {}8@`۽vjmN^@k'ڟ/Yt7LO5H$啔@SBRc+!3RJ㧯cPϦKW,i=r\Y [+~x! w/"{Nm} Ӷ> ?~7^"lO2=w04+h@Ce44]06\vᲿ^zyU(yC,^/mA M`2 ]j2]B31~a3 T9pX#  0gCSqCòd㺶#IsnQIl Ћ½iiF T{3$+.~}Y9Vn8׳ hhldl(CM.W$wDvLA5aꇀS4q xnᾓu]-cjfpl!ʓfy%5Hw+)0iy $V^lCԎduu=]w6=*W^zGz+|։ 3l}p#"<'vى Zqq("`cBBwi窑vw\*"U} ܢ΄.+  Re{mGGqA^@S^2{ar Ch@Ce44]:6vjWm2]bJʅs <;B4lfh C`M=Z>:w;u'37{%z3" t/" x!b>@KEmϧyy$_B OzG pB*:Zuz!E46b }{ ݢZT@6{6:M v cK8ν)^WĄG 4 2c.FeūmOHJ S;@7GLUjYt&I{6d8h']n $Y;sǃ j@&J} ]gn{m1&I[pPh ZR'a|!Ѐ&ˌui02x'BdIC+1`@`QmoMI$5 8v7Dh@Ce44];6vq(㨲 mR{ l{ c8#ޟ:-B(nն9[LoQgN8X}!`@%F涛LAA4eFe>DMkqh M@STk#j.ளPݹ#!@QaPmGpFxk6Z{*[Z}, Z " Rݹ;;BN><@l{/ҴSOz ޒ00844]flH#ްJ uz{J'e⎡E)ib_@,(%v }2mÙVR J8w;H$a>} CS44]fl@C凱c^2l{dtw8wDR2 'O//_;h,N ~5 l;lf ~[Ź LO>}tpMn$1Lo7uIo\Spfh0 0t> M@{"eI-֜{{uEh,OZ@gi@E?PKe' IDAT>-7z$;B|@! qqӕ1ƧwBCӠ \@_᫶ێjoP; xa8woIƒ$a hhXHvI _6am{5kul!G>_N(P1ALb 3~Pgt M@? V^d܎^78oQgn7_p!pY"Ŷ[$-dr 6 :+'p;Q=  0} [LoQg@L>iekg[ǞB^#l_Cb!4!so50Vgt $B^Fu#ƹ}˒KO}8ǴJn^pD!Y:K4 2c"LZ ?_7b{&_ڱf@½Td-ƶۣn5 'H"] MîiP3~x{,Sl R91ڊmEB; 6TY#4 2c1 $N"*ۋpyݸ]dTM@'6btG/~ED{Jk啔@H,GZ^I!)|a5Ulp+RMIC r?_J{|XuVYG5 N IB Tv_$FpR_s7 I4 2c &xm7q1ϽbݜuŘF^B@`QZW^54_U_#ۇq=" tƇZs ꟰s}&(@ %z u[O=V pA Y͹w_䅯 @`'aQPxF_y g+n$Jk }Tlr5]|Km> w 5sܫiQg9N{R `5vTd];p5\wM-N湯9r5 Q{omJCnǎzB W3U@h@JUbfi! oe8";ѡl; hhW:($rz3EaQ%LFn+A` Fk2TM]ٶ=#/@dܛ^SVYfy Iy%5F0ʑ@RVO =|Hm^~ ,A=bO(HX:iek3܏a `dY n%ka_z?syGOd$!k6WahNЀ&]gr;-l{h۞scѧm`lPJ@ T}>^ pA`s~n `@e dn%8]|wFJX}ih8y$i2sowo6Û5z04h@Ce#cCS_1DJsk!LH>aR[t ނj:w8ܿ /@gJ~_e-^+"#0tB M@ Nd>*[-8a9>SmY 'Lj.]*gn{]k Nb!`uvp|GvYsW;ݨf;w>H>ێjwcnaB C$/8zO+C 'fj n3SQwiȶ S75K]} E:$ʾ |x/|w h}Զ/ko3 ~X{i)v/3r`x9_lȃOh7!]¹&_3K:eЀ&]]\N@oWS~ם'`bð0y/}$匈!Mj.=ƶ/] |:Q 9Io>zsoj!DGb[q=Y:>zO+FKX@=z,ׄm@]Ȕ+x{F+IyKb|g}z2ۘ=x hh06 2̾_ boNc{w+M@$p7amgbd-NPyx{s{??aE 4 r/tu!I@FWkGkv0bm$tzS|@d|@ϸ0BݓnKC `-gflq&gQuv}tV, ZH'vYt%TxOzpۋn/'͞.w䠱. $= q]Jm\̟9w[@`|r>j9@wMYXc^?Z¹f М9b ,uuގ#Ϝ{^mc9z5*C`YSvAA>]OUP}sأz] {$=w ah$Ѐ&] XuiwpC!dddl;{B(T.<`XIWGs7|;Ǿf:4>s] pul8^l4r}AG[  J:}lYeTwWvY#Ȁ@Tw@ȓ^<+F 5 A `;7[uӶ9#almkiC ܇`(+nH P]" <=Lb2;g].Khhb0GîSB;Yh7~/N{1zTS? `rvJc TTRo7 ɱ; 7NO$ ghLB{ByVq͚~Mr1Q 1cwh5U;[$Dl{S(9;yOKR Oh03x?w\|z~{1E8ⷷ_z}ӍC:R- @LJZ !RZ}o'=U]j0y۳Oa58'g=Z4:Ӕ㲜knG*=+)tcꀢ2@ =.aVnv; >տKUyә !sg@`-dV n"٭k. Mf P/롭PTUܨE>]ZKU+Ⱦd LB`?M~_;5_[x& =aƇLGC)i=@$Qyn!m@ +\}ph0&)P] >ɿgl8 &nO#νIWz;4 r@kvʷ?%zD дW1I\c56 #P0sg{ >Ræy!S)sdM~@-Z${SS3NڪBֶWiJn d;wT-[v,@[NTlmGGQc kw /P]:*C7@h!DpǑdƒ0aB- k~p'c[mu-><۹ mTh1ڳg1IޓvR䎧œX߅eۏ)?<Ztq\+@ $d$M<0m#*@`wI Tt7ÀKl Z? ܿKQ%y!ZhZǹy۳BlSL@'h7hsg>;>@ &Mf> %/s}~e{ǿx)X `\ #S HvMHOJqJ ҶbO[qc|ճhBsFUqR><ݕ+`  a݁JpU0R ɝߟ; DܫcBS&"p@KmOS@3~h΋Tz:%[hEîɎ*S2$>#`N [nE2m'A >տKUg w26dD7py^]>e~Jpf>/a@; ]en,kse{ԀNP@R 刻h[=&s`J:S-SR'A6.ra; ZH֬^&uA]c|x"uyֆs?F"jzv)5+uz{Sb_֓YBG8DWjG SS"BJn.mgb "<1v)]*K (od np#g" e~M r#mQ,T U;4uJʨ%pEo@K~i )Fm@wa(x_sS_Fνq (Sv)OI7@mwWѾ}FOޗA@[I|`QVCxB`nI԰_d<9Km' ٳOI6:{Noٖ,*!p7kl4 ,5@w*P;6D ^ ϛIh|x+ʷ)EN@-~:pb`(BH pOb@Kmo:!Д}wٞ.5U=sf~ dOI$(v[$϶%iSw $w#!g#v628 $(T.߿ew}N{CDî59FS}޾B9vνgzUΜ -FѶGu 0*aZ^*K <)[d&uӝsOrXx0$ 40:OOA&~^Oxc,vνq;;n@K.wCCN@wi"P}^ 1E`%n <#dROI8hqޮ|r2}_w!s Д½)ޓێj?a4&+ A'zy .5I38nlba04|^+ZB̓G9bͶwwu{|UNe[öwMc0@@=n%=? a=~_;{<0c~POB>Td4u@}LOk TSPl|[(s t\zsO{&:XVatфk#7YпL|=vT{c(r.૧i p2b CjiepqNo?xF-U,p1/ p#a$@+` Eច- l{ -wi\c6=s.*c~Z%4)VC26.wc;~ vin[ìP5 ~<%PY>tF:94>PW1_rJ)u֖s1m8axqubV#po{W0Q/V%T! @w/P`<Ɇ[_{aahiLc:!pKsdCTŶ?mv>T/.W=Rl@K`Q5Rf' hs@zPpjدO2$92*' 0p͒K@5 (.52V2wjmi>8@;dS:P#p-@Qg .${sFp&-R`ܶ#U[$:!LI^ul{&r m=sLw)U]j@ǧ3ʹPH-Йy{rZlj8G"AV׶[9,gr_w$1nYtDFX=3ɁjZ&۞Ɉ 8j4QǦjzPǿowDaƹi؇#%H9}pz=`}iR5-GsdpXO8' C;w=n "B"pwl=5@gܵ'R|ߥUBsL;ec ɔCN%9RZLo^RU<lUSIe&m_VwX&|B@~y>I{j#۾ݕwnϙa~c`l PD~/PӲs Itϗ~i 1(T.#k#OCG.[!?&>v P[\ImlN$:*@l;Z{'S˿\Y]*K (x=:eXx ƇiI(7=`W&ib89Cs4"1UL͹+!<@?g!%Q Q9L =*ip5Αk>|Cmok:&} iB ST $@ T|w:f!Y͂k?$<{MYܣKh ~){ೡ}*lε:B@p0 ZhjΝ#vTQMfc~X8_ sGv02 ;Ұci+N#~jӂn tM{.1 pflN>>,w]g=nߍ0{Isdͼu-iARup @Յ{"sۏ#Կy -%ip-}Z'^йcJvaa@k. pLAk ioot/a-F ~ @mG? >D[)*!.x9.M#땛, #lֶ=-ܽ'#/ᩣ99ιC{$ùZ & QF.a'ͼU`z{^%Gt~a}J&%!mG7ws7ɵ]FӁ@pub_*Bb, =0t{>=C ù H ^B]3_;5K%&oّpGF-6u灓s.S+?c/D!Oгij\ N/C 4QFp~y%5Jk.o UyQbVkHs3>a=mXmGƽذi=YNeg,GS֐)=c؟GrJynIǻK<Ƭ CckBBu')C &.R^̈́|7L|$6qS|3saM:;x' c pMir1:oqy8J`aM2 PT=ZE=i,7}Np&e*P] 3Kgk$Eq@>~|\vrz22,g0n{l@H`m?f-B`E|?6JKs6#R|ߥ_؝{/<@͓Kz҆sڶwZq:ֹ;aBV .h^k|O- փ>x* |^~3n;]c엀i/BdO9.9 aCC 94,8w #Is dp/ێj Px-JGy0$!Uz{RkΔ=@z1$޾`譭Aܠq FUB`~{%W,=I3o QK}=&m]9gۥ=P]Bl#,B8ɭm7ԫ_}o Dd"Uc] ed.L A$@#.f(Uh*KHs1w~]&lJ$"" |s]]k˲1Բ mgbZc:'F<-'PڨCt)8}Ob:XHsDD#j]//ݜ 8 #=۶3Nr͝ÀN\:$//Y=ۋL= `7>B`e+#o&}ד1gh1X٪}a8!sS_)awr}.ba Y4R9Sw+b}IDhO帎mDYFC'FIFfG($Fc 30K@-xz1G[ᜰұ_I^{0=տKǿseyҭj8GnQv4-Lv0Ks7ˁ {ڥ,ڞCX'@qr8Ih$vXQ@?UԀׄEp"o@HGGB.ێjOI.B,%K~YߝVНmo0P]<=?͎D6CKœݫ&.9oo[Xn\L wlA),S4S|!wL~'llIQ]vNUP+~%"LI&4 = =& }ԡ)C0*.>h ,S1X0UEre<)v#3~X}kĹ >տKmǿ>rZq㨥ptNwm[vs ^m_$ # &B%`Q~Ev0fVEr'K. K3Y[E[n(]@_cQ_ƷXȲ:nLqr;zqCV%`N7L=^u f>'`n&I+[[Ve|ajۭK|oܗO {`$C!`vTQCpG}B5 ٚ4m%9Ťֶ6,()6 FRqNKS|ߥ|I| KrJj^)(#դ6 04Z4fM_ZnisN{L8w@pTanCB;Sf"dn:6'rQTwauO.<#P4;}}տKοÅlsԵGWW_+CCtLIh!@r'@B3oݢ4[[88;w'i|.*K >&{B;QIu۾]릣Eu#Lͱ Nv{ڥm t('*ghj*d$tAվs%Ԍ\(T.(irB;vN].Nnil 'tH' sTzDO(^ob.G'eߥTw*!0nͦq(i}(aCI:!0%+m2t.imFi 7YoO >om? S >տK_c<4!nj\7-!A {T΅{%zsۧ|6; @M6Hd HN$CGFՋs*{̩*R|ߥ|N&8i0-l AF&6iu2(C`2'=UdI;#XZGɊ PrOl!#!o4P] >AZܶo?ϒ_XЙ]g2f" wlL٥/(rg?c( ߢ-uYovv4}WEBߥTw!q4&mGQR~LKj8ۢc+l)8wS ".. 0e$=0D7S1zdӂj?ihS.T %>|$-Iۍ |C?@= 8w 0 =YVO vi)[Obj۟;nG.8I ._+xtF~F~hzz2t~[߬Y1uAc|1iXhTP ]ל/Թ$v ܿKQֲ-z$s۷ld$3jƵxah,S]w2 WFG#=U'n:wU(Q]+xtg]S6:4LPV!Q 229rU,³jggH z介\uIZiv9XU/X]ŝ5B=}q~5#4tH&ڹqg", |sŎ@g:F |lߧmE7\uk3;HNMԇ" ?[D]Bܢκښª#AIsL|^]lݥ!5 ȓ5kk@T}1/_NRMc1;z Ϩ!oGѶsol$d-2C4EƨJO`k˰@J8kb{^䕀< 3brIR^SI$VdBS#c |$cbѶKx񲸫Y˜ *!WR^C BQ?Гe >p"ӧyOSEH:ܩ~O%O:>1L7ԜƇ\jxW]="MLX?}} jR>/g @>. SrvgMNlj72}Uw?Bߵx'wg;vEu#6"2HRҰ;B0H`,0{HS1KvAl8wǓ;W Twwd>\gTu*2]*iJCF ED w n-s@@@2 ҷmsdpQMhSrvあw =`1!uRa2 Е:빳NYLb1}Ac ADO6O5^rO\}UIy%i5,~js}%4Ci¿@,|:^ad #nw*}! kO4X]g2P풣9r$2*" ]6l{V 2yug4G+S;py/qi?mahH! sd b" ܿa" \ܱ^w$K%=42[a9K)zqTPdZTՉz>QX}!҄n1ԗ@z}# &$Ԭm/g'Skk0`^ճIzEaHhמwK/ 0YxfGbu/,atHGeX 8 t֖ rv#t'ԬmOG̞OҽK^c*Wcambf-Cg4X[F go^2lx2@;d]y{,'cXlx䄱2:\u5 S^u{*4Zou2~ȘSALJQlH93ܿ:H  U;pwb+3%x ̲%\utս 8ݾ𾘌u\}/h$ c]s2x=Ao2=)}@KfطމT4r%c!RzܫJkor{?I MLSw&c_{yhՊ.VP*Us^%JkB0Hsjx}ޓBCH"P*ܥ1S=I8L;C,#֠m֩s|$cBmd_8k( .U:E#X^Iy И$) |@k@ͮ箃  l7\w Z2H!3 wg/^Q<ȸ((6*Xn Ǒ?B{M a0ߥi !0@Პ5@7r_;m737/^Nyl|cG+᪻:;N^Š|$X}D *,)l￞{mKX*AĞ-)s<F_MS՞}6Gy Ii%-_O̞{x\WT^z]>˜:#5#r92A:n*,)z\XF<=InXO}ϧTTbPx )'S&YV [THA`F-3cVՄtׂsA(!0OC;h\FGMhǶ8rr7, M]u .5(xnx?"/ FI~l@5ċs4ڀ@ < ֠8m R+9P5<ύ_^I;I"U7_uU,.O@?N0dLo. NW3ȎG'#Q [144huF)CjkpGΒ2: 1eP 1-~ /Gg8>$sܷ.gy Ryy%kiIz  U0+PA%|mN_2-dp*,hD9*$܃M LeZYϝ `''X;Gwm9l{۬t݈su'?I& m9gư&WCY= 2}t-@@%eX[fGA^l& [)N|G4fe}NT ;XFL}R)?J! #&7'B7 @&3ܷ1H I55Zvcc$]hzcڕ>W0llp}FW]XEu|Lz1Dz&diO,4~nI; hA{N|gߥf!oq՚x bI| g<  06otg ǟrWk@+)! s>mk$5S5^RkZ [_g */xB:A04hdL[ @6VKl.,#_z՝{ޓp68  WujČ_1v㹛&<֖Neœ!C|J0=ÿkQu%m51;ngx<ݹ4YmPܽ@'N"3|V u[';u>/`$.4UL*R~խ|"wVo{mXۅ6УMh5VhA{\&yA^Ƚe/1_ҳtl{0֓ vsW'`oξ:EwFӃwF%=a4M[ [0137vs78X`R쇴݅Sk:5D-M+2E9$, pC){ȁ۰kX`O۠ et3=foHlh.\wϣfЎ82鑧h &a0=>'i|9 ?u=}+gT"d%۫O[:̠ѐ5\Hpq3GUN)'+ Du}cۭi8pI䪛W]/a}јbt- &U @pXXȨoEc3 3QK"4pm-"w;@B _ #5hNx5EpqG=CG}; N.DjJS4X7HLJaHSqۻ*KZY=J"5Hhs|)C =_}oJSϒgg@ O1VۇQ1Y%v9]1ظf@| ض=u%0^_@8.id!cn dBSS\*17<;7޸q3{7WfS3$0N[pw {,4/3l{ R1soğn#-.v;7VTϓ 4pe\+%ΓEv st;p ܏?z/S!"<18'[xxl{<{2QK#ϧs[궠:N$XZ_))UC'4fsY #=.sF2gdby-@l\umZ$6I|Y8z B P .A܃H`$\l5ܻ%n4DBprf~A{2#7]g"<$ׇOodSßmI%[˫2$!ʌ$u# L`\YX%{Kf0$!@⨇sƜEFFهž>GFfӫR=;>1{*3x._ R޶|jAJ@sW dw {%3Y`}g;<EnȝXϐ 8aeXv 0C݌bd{ n}(& poRE-xKjo <ڲ>ɷU _F EVr~L|P4 sW1!g]@)n"ZۀmdfnlU 4~H܁WT7:h'b )5QR41p"{D6Qlu.-O5`a)Ous:W!xʷ̷}+-;QƸBEU0 Y SnO-& 8  B\\ [hY/Z}D+\u42!Rc  h(o1 ZĆ" "̼!7y?ke~K ctv ~"7ifvJN2E`b=sۨphA58cQ3z:қOϠS,z&| E+o4XP>C46Yz='44~sWkpṯٻQ,{mdt SLϽ3ᦾOg։eGţtLH}B(&{^)psX~gY/BVpG3^uGb\m!SO7pe(9hxFds_w/*^"ISMvz~2v݈0]u3^EyAt~ !/7x x)~ Rd!PՑNx Žd46ư˿?Fwe2A# C pe/7]d%@{T͛Ғ\*IxxlX-F1br. ě|we(3h8"wx Gʽ^[L@XyT}c= t,SWݧrS$n>^ ~;4OsLPJ!! IDAT.fr(YEx; T24U* nj@P#R!JG[ՙ$RCxx  alCp8LdB)x'1#.Ng2`+?h|i.@g.vnml#c-"oHE Ng-jUcV9%0 ?SNq=xHi!ކ+Y-pkx; 09,,*,l/F)C! \os6ǘ#nN)UW"AA/(8k,;5fsV po}v+cg1vnj7F뿩I)ZJ9 FKx.ތ?SxRrZ4UobA6@`"xE1V4,JE֩LwزM[6y˖ h(z]N`Q=ȶ>{R?XэX'62a = ]k,>jk̙3+1" 'H5:u DJpF/w:B@Ԛ5^] <3 A1 wmg̡TaD 6܃8x;tP}l0rr븂s|}チ\|Ϋi]1q,Q}@J@zE*V7/o^%@"!U5jmFsD4&=1 <ƽ6=mvEӞԡݓ6cAxu8SoB"ړpq3Ϧ0xj9Z;ZԪc,m6pQ>PЀFpN@ /jjr=RIܟ)4I|/4DZQ*J ๗4s>fVs5iff$1 P{=dGHhx2>< Y}*:{ GyxɈzLI3d %@ 0Op㹟@Y驹6az ѦֶIiBqxsAg'09>;c`Z@9Fq?As,R&hKUq#u |qRbo1)Ϗ-(/x `hv;맟n1 >{bKp`&bu a VMClw?)5Jh๫ `?b{8!8^zE8 ۏD?.;zaj\}Awn@;>C"/T/4ܕx{%[L@`8|H X j>n7^^Q١qEj@D ѦC -xKpT=Apca>*ٲBJB E\AxR.G44xXY=M*C]m ,uyROr_JΓCI\;z8mM~Nˆ3̎ e_%Hý>{>NMܵoMkmdrCw59Pl iDz>U񚟭{4}Di޽DwHx"8W0ܯļCc6ju) #=6wѪ4h$yK]A)^;m4 p=sY8 h;E}W OIDCE.Q\.d0ᚆ S,r?“= `ס^#Y~Pu6$MqLfbs R.H ǗbAi4 >|\!0^ ȱ:j}VC3gTALJ<{}dl@PJ9Q/ԗv1Nl(CQDssS}:a MACҞ 414棁箚w 2{$m@@zIia[B5rO Uo]5(i_`*Eh@C hPo๫ĭ `"ފ,yXfR/׈&knr"$ZFڼG!4T @ `WyL~$c zu}v7 tlh{a`SaAGAY>+hZV*ŴLp47@C hGdfx;%U /<@"!Fh]FOՋ h( A R3gT)#m^7 @` =$m^KKo!YBx&K4C2@Kڒ.g$g h(jjpx %n[u~~Me s٭@DPLE h(o4ܵ-3`g@JEϝV_2 ^ n;?g@E=W{<A"l `@/܍ :Y9V[u@wzwHxHH3(Z_i hOb%b7`T/7pr{41pDݐҒwz-N;X7s6pQ '4x:ECJ/c8>STFh@C hLohƂ({"0AM2U9;*$jw#D?Xz s}PC~@D S,@ @#j΂ g=WFkEVdʋ>\{M5A i |VQjYFv #F3EICJ@cz> }+OmG>tEOՋ|284]xv1YIYjeJHY%7y F oTXhKcƧ #?Adq h(V4ܕ,5 k>#_[#kfl" |-A1UЀ ^㹷MڱH{&d+@?Z*4:0⹇򑭒}b|FhhIXܗle읭CCE/%8ԅ퍝s2Fn_)CQ4x\>U31N-b_;)ЀИ޸t `3L"P=E *nl,mo[ ! 6p !0'S{@Xn۪ͩAZ5 3 rx gpF\3c@b>=A9c*[h@C hLot"wŽxP&㏷ pT_7KFlMja4{MYP۪EN @xU0:H>IT֛ pW᩸*$41玗} X5?)ЀИޘʶl>xK:யԪjS447sW NDsw"$H ,lπ)*zDX\{dp)K`|luL@h@C hLoLL][-p8{lŋxV79*&4<Иs_ҋfkREy'N*eC ?vh +^(!Sq۟"<&Ol1t{@'f#h@C hLo%;$MT45l p1WЀИpB]c{ݳ̭;EnpۻwBL`E;>A?|jB  P{-`<֌q^ ܝ\E+  h( HԷ99A pJ'mZ-$J`;@+ T O{;gFA\EWh(mh@C h<7Xb1ipFx xhda{1{@@M<&|F_g^ ˿-*;Fpw(j)LEZDwMxX}^,фKP{A"Zfxm\UOh@C hLo( '1!女~ S 447sWIn{,)@7,l/S!B"]9{y4X-? Ё?dY`Ϭ^n{\rr mϑs k4pp}Ħq i'3EPni๫b w"Laq=@[ 𛰂T@܇@RnռH"k sWaf1gW0~<|v tpM W,mཱྀ5ހP,?U{Z"Z:x8PP Pv &2 D`s'@@ ڂ'# %h܃?uXM0_?|ЀИހ 6Jݨ0s shA6ɀ)@6<Njk|F|6p=* LKEJp 8|羦z87,l/!q?š UP:;A3D+@) %1qN]LcO*n5-&qۓ$`@$hta-tfsJgVx1կ9c{(bFϝMwku625 &GAJU\'3g A O-9,~Ͻ&5rA*@WUd pBJ@Xg"|:@-O(ͥbh͞ E h(+xJg}.Vޖ/!G >pjzLn XshA#f"{!LA} pg3xK-FƒnԲXƂ{8Y,* ) 447bcϢT:f$<#Vpf `{G]ͨ .~"UFh@C hLoLC]"pBE&x$pz+Kf_dbPЀIo,F^LI{}"puB@Oݿy3sYhG@oYގ3h/IPMcqݸݩ@CAc(u9b4Z4Xzۏ1k4YooAU[A*$I@U*)@ V{.9΃  ڃr1'1l qT;h@C hLoH]U7cJaxV 8v'kvٷ}UK`&? |eLfLݾ5_!x `al#L5 b% h(c{}sW۽n/ h(鍥i๫b wSrX/ݺBxQy!k1cT v3~ȫE{RөƎ]mu#:mꁔĠ %1 %@\{Ks_Jn&; ۇ`gU pwCJ(sLJ~dgtj(,r)%M]|Vv׏?>0(p r:FVЀ7x {K4̢OdLQe %1: NT9ϣ# P -iE;7y u2Th@C hLoLO]%c`gUm A%aSz`v}~dgf V~|G@Cy@J@u{]`l>ܣϭ 62SXk}u$G3O'5oT}&b2 g}{ms_Lp[ k\&_K;vr4G)vpV̿nTe(Nh@C hLo8B1G3.-NoD2p#Aq  #_b(uߣ/o.xõp.}-U62<&' Pb<(gOutash@C hLo@C &>X7Wۄz7ig",l@#nQ44. Kx۽ؤl#!0-tЀ]q<. ,om h( h(/1HH1YU( gh@|# xpxV:;Mxt=DPШOE4>\<+&zσ ܷA*lh@C hLo%Mý?s#ט[>LΌ ܹSҖ4_z]$խҤ9?ʜm=h@C hLo@C b<.{ n_sl)^Cx:E.'B$'wm 6\/3ܖb|LP2rA 8/gD& UP \7?̡aD #yކ+Y+s2! {,9]`B`j7|Ѐи~o+߼oqyV;kf_7  h( 4UF1{#(YPvaG?@@!pXOq@-ldG}5AP -O̷8?Z2\7(CU4ix*Oý:Rs?2óܰ qBO$ @ S]?)j]D]dsGX%xqý.O]sc5D,lWpG P 3'2Ebm} h( h(Έ13qJ}<1\morqo5"{7'(C@cix*xoMQ,8-'!@ { G`?" &w9VI  |xی>}D밍 Kgi@ >j z2 4x'P=?;a\;PP ׅ*bm@0%ZgܧԬF( &ޛxxՍ3v.vx;7p_ 1Ӆ.Vxܧhx#O]w#NOk[AJ@czCiXvog6w P -GxǞgT-fɀ)GFnQ(Ce4ƕ{E rnYP[o*asP44zcq]xbxW: 8q @ ?Dc^#*jGdFɘ'>}2_OSf,'Um>Ǭ244>1ݸ@ ;a 24474Ʋ;6ܧnl#s' @("^ݑݒQ3b L((CC?5=w wlb=}fD3k h(=E~- suZWHВ!4$.G蹏fvԁ0rFЀto๫T6lYhթu&d‚4u .rpצ$On1QSl#/!0Z@8ñ OΌMU0 P/x*D1e_⺡/ch(h@C hLoBc)]ۚ<mQ5j @C`̠*54474>u5gO @x1"ށ310bR* h(:8U ۽ |kd=+2 4474n ;AV.GϽ,loA . ؞ e h(' Ô5;#虂aϦS4 44746ZRý-_OAϽLuyΞ]gW!0/唡N4bm ׍H< %11ܵ!P-ϧO}62%fpJ*Z1Ce,u"O:-ZNsV % c/ٶL@E!; ڊ' 1)C4#]J0pӿa4'4474N=w w՞xix='B3@S-.JE8  lWa2^H{K-l N$|CPE{}; 5A[RENQ{%ėBJ@czJDy]T,@ZE=w wU ,?^06+ȪЀИ8wSb0  h( A +=w +x J`ϝmdTwl&]Wl8qܢRЀИhH#s5ZS%Iມ h( h(&4z71Xsga&e@ pI sYUM7O*~d* @ {.o06{h@J@czFv0Xz쒟b44 1U*bDp鹳L؄%]rCMsv2ZHDUЀиfo1Ulb$vӧ[!ba{ "P[A͔ %()VFk[2ǝa  J@> !;n{LC8@ULh@C hLonwCb@synWO#-Lcv"`UUްq  h( h(T'pÑ,G_qRX&&<ա6@4}U6y @\.rp[4#{`t݃Վ^:8%8Yt UPi4Zx?Kạ] h( h((=w wUl]XDe2 O`fG PPh1Ucb}i{4$lfȪvЀИ0HCKʉx9q @K`WdF[=X ) Qa&+ P4Ƭ4-r߈];8k._ژЀИހИHspWy!Ppd @Ck؞9vd &spo-(%sDmg#n<x$UPhtܰU`@[;@J@czJ@czc; L {l#SS?rA#5x1 `|}<  iR?tPЀИހ87y 1*_/l<1AA ;SB(CCJ@c#%MܭӈաP4! @M `7Krt.e%QrC3#& eЀxն#%4 4474}cOSMOfe; pJ v?U哧_)%'&@cLs FuFsvW V:\  h(R=P L"aU[h@C hLo@C hLoMA V:xtv u,=N0cRЀИCcbkOepPЀИހИ@A {g@"~!'0sp۾e D;;{ܫ$ cUFP_h ܱݵ)'VPPJcX\sp%L}YzTh@C hLo@C hl7jqJ"Q:#-J*44747hzʎs K;@f@tq[ qXTh@C hLo@C hLo,Gsp& Љ*!6od" h(uiLٰݵw!@Lc[ 0ΨX& h( h(zcv=L]U*j3 e h( h(c+8b@;6 h( h( C4^_}[̮XxB`[,o *bF+~jodzr#`o8 'uo fX7BLj8p ΍H#~i8 _}=[[Aݔ %1ao=vnˊy*6h@C hLo@C hLo(?䝟,@`&0m˄x!v7TqB3@K[GډЀИހ[fd0'q4474739{=<}ˀᾓ$ '#swcB 7s9nWY@!@<WM]{'Y[Q-evX>e  Js-kK@@(@ڪЀƁ>,gEU:4~܋n?~|}1 3QʓgH3 +@"4^_!KO(wCZ S< LݽZ!7\=۰v4474y`}r=p*o oyh@@@3ТȊ4L!.j"{{-ev@>gǙ/&&Ӆ o  h( h(+Ʋ{`Zvb?<_4.@H3 +@"40e}'rpRfGAT orLZך744746ZҬf^~ehCAJ@czJ@NgO4V{xa :p!3(@ ]ur oVpU^jbKN iRf'82ǻ|"Aao)PF_ %1 %qXyclXRXSSi'4A˓gu')@)GZQ@@7XyC-簇 ̵];`;a, 8f"`p4};-bTCftS< % IDAT1ќvpަa_h@C (PK)CIj)}jϷX۾ ǖ2;v@2 tpӾ % h(Uh̦f7 h(D Ly{̓ɾ;mDV 0)Vo@'ZĔ@!u2#0UT۽pzV4:Gqy $($H3 +жEqUo;ahV+b@-ϟ /?vxI8CbBsW%6%v  ry&50V{8 S9xF@7 I-{L"@K"447֢z8=0oX PP<=Lc}ڷ1w V[%/ @KR*[/&L~Э$2^aW2PjA "ފ츼l3=#B)AJ@czJ@c#%;"AKFz2T`@`28%rhj a&Hjx~=s>VbbP: ,+_!U2-\< ʁ>,o (@U;'䏦>Diϔg ˓g""4'簇/cW[|p[[pg VXފ,y!@@Gl,s;()/2G C daP؈*p 3%$.o?ϿV*ЀИހxsW!ѢCCICJ@cz GǘOYp[l)T2En)JN.BiTm)ؔ/+@&-GZQ@@7XyZT2d~y։l>ͺvB$(r9 (3$(.>cm&3phLyv]T^M/ e h( h(z]=$cpfj 4B$( t9 2(y&;kJ?4趇R&^Ax{ ~7TNXhTe443{˨r3h{Q %sLv폌` ֶopS xJuv/~@pEݕL3q؟qMx۱ڷ9L ;l)S!d+OR2TVh@@@38yC~k۷cWoB{7s6mDuI۾A*J]!TR<e7H3 +0qk3ة>qU (_8۷7Pl)3a8!2'Px L_y<Ѐ7-Wq#  %11<=]ŶOt +oo/?;^~`^@b"79JihIEx?58y&;8Ş|ѳm&Ɩ2Oe$uK2sq]8!J,Z~2zB%I3P#-π([P<e69y/GB$( m9 R_SOn\V&+=5$s˄݉L =U*^@>W$ 9`ftCCA!'ܟ[?(rh@C hLo@C hl7$b#eh?@J@{#da׾(v5B`-Z- wJe4U*dB{ IėBJ@c뽑簇b3}҅;ypQ@`&pW%s;Kvd HPala(B%I3PΠ7([yD)⛹Gٵ wZf~a$BII3Pq 5BY3>Xn{+lff iܯsg3iģP$`}1N͹> e h( [4XzXyЀИިIt1k#Aumw`o1C We @Ks_Jn& @`'簇1w.n{-e\v,O@2]Ƕ{e!$XVҷ Ury8o9 (3ТHOIWxLd~2.0g˓gH3 ʺ8쇎`RWX w+JPخOmw2JdU_*Fh@C hLo@C hl7~߭鹫b+Qõ/G e h(Oz]_8άm KK~c =J ,L}a: @2v?M>@=58gOzd>L/*JPPW ͡ay $(CNh8yI`e= p-U mJ%3'CB,'hn{ 64474=w wgqyg5')@f)GZa&Qp >V{^–2u8.{oo/C@bceЀИހиIo"NB DиUo92<%nVF%]ĹC?D>s6ՖR:#Vc{@Xp/OR\(CUЀИXuV'52IiSi\ TΞUrBn %1 %p鹗${C bh(!h|cLxVJ`ӗ]۾cKx'}vSϝe+3@BH244746ZQ(C[P'#6H/noalTJ x&" @@u6s 6gOA m9PiaWm?̖2Xx R~,?C{룏: Z-e*Cy UBނ4r4m,sL|[G'uH|JC\ nUu7n02ָR44m{ÓpE忌)C@J{4*lHcX!mȽe~3@ |-Sn T88&`?ES1oAZ&fy{Vލxe(h@C hLo@C hnۯ ;$lʓgiyDA}X`ks?roγ8 Y _֛(y{~ %pCbB l_ 6 Sbs]9W e(h@C hLo@C hagővghS 6mw<x  @R8)9"HR&Ak[ܣ LLnvG)G -e \% ehKBJ@czJ@zcލetK <2+[!TR<eiy9Ds0=I 0 x)1Yq$ooLx;.th@b{8ѡ %14B\<|eLOh7<\"?P Gy&;2%Acʖ28%}y1bLlX੤ %1 %@\WH @;$/npo,r# \rc)QuX^UH h( h( w$Oe@INe=s;2,xR&% hHrLm@Ccj.^{ˤo)so? Q6䱃(5Oy~ `e5wR)ukh:= tLN>"HqCАژF]W>;y {0.2 |NRN.B`y[J0ꒀLR hHrLm@Cc#!]86B7d .SvvkGpPhU2Qfbb_{_$E4$96_TMDeӸ\$qL{"(.@w_+;_gPc"R@p' I`K A>51~2 #[-xI!)y/0JTf>ƚ{27p_"6df碑^hC;}%==JBhJ`y<ϐso'&JqXАP I@ hHr\6޷>0SϫpCH#OdGaa fJzNc{ ^ "ϼ@gy;{1= @(A`K̏ Pc"C6JaGm?˟!C-ersie\oZo&VhKv! ASАnme>7R9Of.@_I3率1m鬸Q'Gj;RcvL Ў\pO?=sX Z;.aS^}&!iASАxX IINKy"; {rUp!BPP۝ip(O }uLpn ͽ1S"J@ wNӏOeZ@C4$96! AmH=ܖ@(3>*a@aoUB3Ej;R{ 4S J@&$Md˒lc)@PH| T ܿ_mYtw7Þ[@IHj{4kGSe pL?z̨a1:u?0v;~%~{o6Sg-z ވn,IH I@s4Pw> ʋhI]YވB77."'=@IHjS^st7c:`gECI4Ѐ$ 4$96\҈6Yc8|\3DeRi(5cP{)Rۑ{%ȼE0bB } @sx3](ӥ|Q-grv2|cK hRoBx{ nKlL Jlz#z Eb Y Ѐ$ 4$9vVYCz4^ 9b[To!x7Pҍ<=x@R/(7ڃMzۋk/f@jﹻIsN {NSCԶ'pR},z E.4! 1!iނ?7j&wI*[JrT=ש 6Hu99Ck!|'<ߕݿ_rϷOx8;,Pݪk!0!ABSА b=QsSÜ:@Iw>pN.A Dk܃OhV L@SАIPsEcjC>.a1n@%dSDR{v9gKp@-el&)b3ha2kݎXmw?0ވnD#[ )$%" @ʌ9˴zbeK4Oathc_J#;Mn?"p8'@{ @P8LEsW?<EHN$I2Z6n9;@},2pCfАڀ$ S.9ÂȞǭ](5b;HmGjS#*`?p+Le$J`#hHЀ$ Fy{7_ĆiA@aVQ3g1(=7Bp^1qfa #MGbi/$[-OFp#p-[DeFqv? }\HpwSy";=?Q3"p K N@sNCܽpCR4$96! 1F`5<=@@dϪM(7 М@#7π ܭd? b @3Qk%=A^ ˖2K8 2lboMGbg tźnDeG@RHJD@ LoFal⏾kr")([J[&#T y {pqZ%]~C`Hڎ>dŖ2gq= -~@ `Gq7B>Ž^"=!o-|DpoA9 D# '`o@ IАڀ$ CՆZsH#Oa9CdGIW#p>`K=2vGy@io)x-FDWTo<%*3Z4ܣ-e?:Z\M/%=1\vvex!^^<駧vN!b_@Cb4$96! ȱP294Dv ;JzFp  G ڃv~0M }c\N\bܐP I@ hHrLmԢ%%Oa:(^C Dj;R{uljp i&H@& k(鵲]@:\Ǵ>f^ pDQpUMQߑ! ASАȧ]ҌߒbAaGI${!"=xHo4r?j*8$$L.J!u?0.ǿ=վ6pMo[-F#[ )$%" @ʌ~GQ kr?<>Ǝ~+ Dj;Rp{@UUv4nAswL4WC[,G@RHJD@ LoFu KJȾG5! j;G=wA'+]sWFy_ ^;[HH IC}-P2#8!5w?|DR{$p\{S 8!'ve2}L: @ PEa?k!&HN` L}ݗ|q싘}A[-F#[ )$%" @ʌOcsOp\MD'J\@ j{:+|DGd8@P@A@ sSCskc"q:N{WL,tgasvSz{`~m IEL{HKTo7ϐI!)y/0JTf4u@Dj;R{%ΓEp$ф9qryI{ݗv|įRzJo!x7gMTo<%*32><=!T%^'L:!&;'o;ҎOx#hLR4$96! 1qF !K!!IaRzې{Kt@p0!iǻ̚1=I/-Fy#[ )$%" @ʌ@#.)()@=ڎ^lg8'>h;ҎSu#h@-Fp#=RBR"P_`z hx!"f @#}hOw-DCH;è)GĊ kL<@RHJD@ LoA#[aߊp }21?A`˚}|)]s~~zyyOO?<6 0ES[pSАڀ$ Uj#Od_%EzLvvY RJ@@v5Fsυ @1<=[E#?W1 Pۛx28vK(kTM =; H{̟p׷-ZG@RHJD@ LoIr#WR zVj[W\ !IaRzېs9NԎ  Klsz=ݑvK^m@ H 4! 1 I@ I#@R(K[=I4wɍ1 "*mHܗݝe7J8 R@'DziCo7Z#[ )$%" @ʌ@#FLﴱG/w]=|Çf8 @% 4Ɖ Ev^ ?DZۥv7$@h@CcjYy"Y}Hj_/`@ DR{pYsF:;Q;&p]RF"PK$:"V=dC/-F#[ )$%" @ʌ BrߢEھp_&#@e Dj;R{YX'χw!`@Dn. jcz!@m_Pl2z aj܈j@To<%*3ҁFG]F~w.@$zX8E..@J} ٽ" yPPs""Wb֫oz Y܈FToHR1 n*FȮaqԾ\/kW ( +r{^#F ,Cvw{=*kI |z> hH!@b$4u`su Dj;#%W,tBH.ixmr1緱~G./~ވnD*=RBR"P_`z H1y"{m]zVm:VM0c@g DR{,C/H LLA4{UCЎ4@N[-Fw=RBR"P_`z %z+L⸥¾tVj_nLÕh+gNvST * 3WٴRs#GpY4z u:#[ [D@ȾVLn{4{T8@`jD=;gZ$,M&sϷe>}zBs?@@5($* hHr6D DK#!@ Hjm/Stkq/:. 6^,e/BDoDo7ϐI!)y/0JTf<=#/0#M v>HmGjŻ 7th&/"k~467BpJoDo7#[ )$%" 0H^.RM[Sإej4wP۷L8ct  4ݏc 4@p@Mz )܈CTo<ނ-+8j{v}}E$I-z%FDLX֑$c?wa .̵s nH8Ѐ$ 4$9nTy {pԋȾ0-"0 =HmGj#B%tWY"Х]s\Ͻjw ׯr;?*"-Fp#3=RBR"P_`z K4Od˂(uhrIa @pDwGU ,(M$O//oF?}z؃ @vj|vwe"=Io(*:J&A{Č.ig4(V=u=So7#[ )$%" @ʌӸ\:2x跍}`%=}>m8LE Rۑڧʾ`p;<@*ٽ&x{D  E OdIa鬤)CYgqtIn@ w4ajL{t g*+Аڀ$ j#OaA*+;܃4bC Cϓ"Ep 0dw^6/ ^-[ZHH IC}-P2#hGhUe:젹os@``ڎ>pG }װ wdw^0)a"I6Ѐ$ 4$9.P˯bxa"{~7#>^N'='L:!C:e^ -&>Ԣ߈IАFSd_Fm_c@"=\Oo!4.Iwy+<$}>,y"@{tp[_xTD+[-FTszz $D䡾RK4Od\a_Xj/}M"@`<ڎ>^}t| qdwSOǸ{`Wp#*R=RBR"P_`z ( 9͸\-(khs/ `jX] 22{>??\#+ؤBfАڀ$ oCKr^j/hXb0A ڃOH NhNckrn{OKBSАXU_I[cP!XS25 ڎ"k8B=@ٽ{Ig`t=QE-FTzz $D䡾\hBEdw8ߊ ?@m!S@pWV 0ྐCvQA9o,ΌhM*=  Аx@aYo0+/܃e4@%fj"ދFdOgux=~,.!>O'} |FܗL wH4SpPߣ#[- )x ,BȎ.{HK@`&BU@:Hj^:#ޏ=3C ؂=4T7CpOf)Z z ވnHpDaRKdv7_@ZW=89>2YDp[( no h@Cjr21"{:WUۣY:>LRf2Q9vۻgZ@poI PJ@朘@sO>ͻ;'Q܈G@RH+kP]&!F45 "=87#ޞ{{\*gnFsL{4p#G=RBR"P_`z %'.3ma|Jj_n$Lp1f"H} 8M}A~t:Uo@s=?\OKh^[[fHH IC}-)<=Bd55SZ>I?k@]wΤ L .A`r$a~7~~nI܈-[(Vy"; ,cھIBCs_hI \ [@PvϠoS<3܈*QTo<X˫ b IDATK?(쉠\VJj~l,e~C Dj;R{Lab1ǵKhQ# {z#z #x_$}-4-<=pPۗ>FO&S'>P{@Mt]45n) nHЀ$ F<]u:@*=F"@탥p }'7B0H`+* k٥秗7?}zٲ@a<9oH #6q^"|p):ܛf2 =M쪹w}7p|-FpC 4.JIQأﰗھ, Qp "%Izfm`Ky+@E{(d*7Cyy[[@uy {p znlLWj_ i0D Rۑ WXBpGp?">5`{efp-F#[ )$%" XȎ.N2G5ϐ;ȳC q  pLW@;$'@pW&J^RN=3k.-z#z E(bH,!?h0N$`Pm_={.$22DR{@o{FnT& bzVs7(mྔ^}[-F G@RJQ<'2RJ>҂Gs_8E RۑOi 0mL`2L3C[cs?wjY)*= Аum(2gj{3^j@pFǍC܃2+l}6V@~܈>zz $eƤ)"{T. 2ýw&wI1 O ]HtK! ,%ӌ*|>=}\aLnwn4! ȱ@ab\S}oy?M 1 Dj;Ri&pI@ Po&+=rʾn=}\INoDo!7﷑I)b!=)f%Zm_ȼ"_0^lGX8"!A h K`Y쯚 |`lGjS؃7R2a( GV,"=Mo,'p/M{Lʀoo,vG;p)Է-FY#[ )S'%OdGaóFڷo t ܷ  H45 7]55vt3h%$PrWz #[ )=|܋|,&{ćCLN} ܋# @pujzp>v^-7d@cdy { B, EL"֧G{}ML  zgb/ER@``.4;4sFPEtU!Bjr"m%QAqY@)Ưu y])ڎn*;8Ӈ3ý{փ Q܇pI/p#-ISC؈Q9D`B]_ѰzdM#PB`Gm'Dڀ{LKTBvoH]њ]\r0[-Fi=RYf L./yX܃kh"? !0 Y2M c"M[J=|yY68& ׯz;8fJ@Ѐ$ Fk#Od]qc S{/(ч6#bB`lNc&^~51BAvTnL=?4Oh2FPᆄ hHr *Lj鬸zl @ @$#y$ p?DEw[ ORTDp[( nO-4LQew'*)],&|ؤ=!#H㥘L@p71cF]@v_񏤹#i6HY#>[Fp#JނG5ġw ^ũ}7un=B!F">R6wiI X!/CsHm_Ғ~[Fp#JISأz I&C B CTZ°M  ٝ]/NC (󀽊+4 \ڎԞ pqý/ّݽ~z /܈ DToR.qI'pAEprtϐeblX??HRL@ OdGa4}< `@$m7(d8 @{y\Ms{s^h}oB#$TZIO%1 {"(.N=#WqO"41iڎn:[879L@p%MvQC܃Gn>!)q$X&BdO *n+'}U #)R6Shs @m˟!p],QFS­dwG;nuz A܈*EToa/)y"; {f"ڞӝ|Gs d["=8Go ~8vB `-M" VDT 4*S؃C2+$.~Aa#H2c}hp-Dvwný~2Kz ܈HI\IU! {"(.j>Wab^R:,n&0N{ p.l@p ܭ jz\oDo!|Fp#N#=m!OdGa2!V+ *nv*EB'Hj3 RNCM`fݕ^^l|<54\SC2ߌ!%ԾeWX_ӇKI Rۑk6TU@mcE6sC(즒3 7KP qL4&8A@C-e4 Lk2YJSN[-FmGP,)(Qv9@U^hK"_밪M#B Hjކ<@ {6:n@ۛrHݦVBToDo7DzYd(6DP\bџ ̯]vDs" v1RKZ@poA5 P::v=RIQأ[{Rƫ%"밍M4;G[,C*pjXT#->P >[!A#F#KČ!PS=]@ DR{pY`Fd=7B:;@0H>w#?x¯_up}NH;Ca J;#=ZD?)v߾pŌ#4+r; Dj;R>.ޅAKxH!?!ϣtJo7"zz r> ,J\=Gjj}E\ͫ^;Z~6"t$>SC4a=Td7"FFdOq!8TixXi?)GCXh" !P@$  =3' jq<~Ʉ(CȻʊ p:y"; ,hI ]j_Ѣev4Pfv Dj;R' >v~n4t݋4//7ʟ?Ƈ#KEz#z !܈>wG8#gv HU̷tb {٤=/_I [cx$1kXB gpRpo{s~xDpNB &H1 !0 h 0)屘ͤ/v( ܧOO?C!iWԎ 3讶=T7 ~]` W_>;pp >bVi\:eAlT{@jai![o7#o!Oa.K*Waĸκ&} pAI?|ER{@`$&""/8XLTB5^? 4YPL 놃[<=!$`Mj?THWk¨ ȯx]d?)JzJTڎԞk 01HH"pf)jqZښ{w|{{"kd"}qH-?TN\{:바M,FRSAmO5%1  Ӯp,QO=~ѧR!j'CȚ 7H>ZDR{}b  S"@s3r5f'CaDn?=NC`"vOòc_{o ]`MB#@G}4eE#6SIszsfm)/R[(Q7@]|DB`vN5m(V\"?r»a hd%_JC`6ex} K,r|۪g<=Wm_*FX[-r#OdGa-_LھD:Vpp OI?LX$m?% 43,r,3!4woe[p\r+ v3p8W'V  E~w#t͗iPIOV#@M#:@`<:goiZ?O//o`}oMr Kzs ϙy {@^^ 8Q-NC\هJz6wlt >pr m@e;% dwIY`{øko_R_-OF꺑'/eC G,Dr؈Jz=Б&m,C:} o!P@<:2 7/O5wEz)~DDFI3H  '3 q&􎩎v`j$@.Dv4%з~ j!.}6:CB7.g?(yܸ ̣>KHO0QýuˇhZ@I6Knn [meޑ:[Mcx^8m!OdGa8 TWp%V01 2%{#=Co?j@pAE0RV>{DWKKVsrv{_o!#Oavo(J`r],!}Ќf{ZpˇAQInڎԾK7!05p:C˳a\P@a? rlr#OdGaOJA:_g 郩fS@8)P] '!`FH"о].i0>K}xs2_-sg!.Pao ȓS"E~YS6 |\sއY#=D@o42@po $Wp_wQR׏;ͽ2v=̪BO7.#{mwpM=pm E~E/J9HmGj"$IFXV;F2맬ywz Qw!POAv z"?£aG/"_ӓ0qQ!@b`RN(B`y4w)5FJhV?e7s??4K.d'|k,zF<@d?@m?˶كYǸ>~t@I~LڦFMeg `wxONA%Ͻt{HHwMBa7P0 ԮLbWq"?")I q~"_*^ {~I$jg΂qur#?~yzw `j{,~)d~JNقM4dqY&EmoCY 0J80+/-vQ«ZB5j?R*Xo!87rB|Edˀ8 0>9U9~'4wQ& {@mI!0p+D'l`<@b?4t.OdGa. ۽S^ǽF8"4}@`{txp% *>:~5p @ 7`G<ϯ6M{cДMܣpؒ@$~:-\Y%q 8ܗ$Z[FV~ljYZXoy {6a? 7vQ\ g4wQ, vv C@p:7w# ajԏ5R\2rOž[ P{eSĩX '-M4-UT%^/!03p9F F2Mi.-w) B *RfgX?5,nAO呋Dj;R{Fv g^k`-󛗧{j{0>+n@@3@m7傏s³#'4Gp8_j{ χw!`(,"S\shr"b#WD ~o0[&OdGa@| 51ޅý*^C0;%MVRAaߟ -\ž[ ؑڣS~h[8L ;Wթ{q Z: M7(vx8:@p)!M=Bp8'ؿ~8Oa"0@D ]m_8Yd@FB"*[ 6#)#> `@p&cs'$_?4Qz@F"%j8Ы']p;b3o6{hHj(m J@S;5'#YD $Ukίz _s:ྕkd,h5PH24u&"<'.~&Bi׏;-@@.o4.eBN=v.X06䳠O^!|-~NƥM,hMwBaJRj:_]TJb?D{K护3B-ex"2ي4 jh//7H7 Wڷ3F2 8^E{܋,nPp gAsfHd@!e< @ 5QDj{Q. `, Ђ gc4YK!JzJHұWFd엀OYQ0H'QgJ DR{0Hc* p˟!%Xl;X3~$@O˴q]c'[&ÜɳT*ir/EHmR;j{3L#^-!X_[f6޷A  w],l@(2{b(5Vm/e;@p˟!XZϐͤG|@I?ޏ蒫5BtQ !lឍ!,v(Y{6nϒiKq)y8YQxL.{Og@jπ-/tB= + 0w>w6sp||#Җ,4&0ھdE*eɽj=hkq/ L' `+[+4 l=%uYҿI5<(ekHܭmde z2 02V#gW[&.JC4@gS׌!( ZdW{+A.\@pw.@v7+ܭDN;InV!0ԾdE V`,~CEWHŭŬN{dZ%J&*53<(&\GI=mdg:@w# @=SX:4D>lj a̓#o!/ͽWoFW.hL1p'v $/O АkK(!4Uz @``2pL h>'_ͅ~% Нj{J x,I454GQDR{v9@,:z@XR}lަRRzQY Cmdv}+4hlϓI69ol_fyg٪ r@`ns!&X6<$o3e L]TT=1,<.|)evlzG=)AF ,Oln4=b3 ܒ[ 00層 S 48d[ & p˟!uK=`6N"3 ^x,Au>^DQ 01H3o%3h)bz9X ^5[_눞@;gr#>4s.j], @pp@'=7)cbB@^$j <_=Y#OXvzOb@`H!7XI{R;@ &;UtG,O`PxŒ2Sq  0-iSOu3d?L&@#62{tI?I3{[r9(zWe@! yaAjGmf^@;w@ yJ'Ί+!w^rKܡtj|L;UM%g "+_x @!8|ap8&ur( +NHS2vvן\ `wI%@{N@@ rIIh$*6NFx9 aCrJDt[>@(L gXfM͑3! PھN֊b"cq(fhrGm‰J*^CInO%Fs/A@g)j"?[SUazrZj-!YüJ262T;@6 ^A(Fn1= Μ@AjOW)ZQ 3(~DG}Rs64? T!^+F!X#CJ@dHr/}LBg!Uۇ @ !n'x@.ֺuִܟoE #TWGWc x1ڟ%s 62NCJ*^CϑŮ=x{i9W@m#8?[;pENAN 04"!62- '2#Z\J\9DJכh@-4~LA5A׫+ךVm> 0 @ 0'2gD&@64_0g>@ S9ZQ1 F$3|ˑ"} `s8&by=P? xL*!A3>wr*e)\e/vv '~ g!Y3c'>ޅhؾ߬-gȩ Z?TQ#}׆}@X \V `r9G.=!%@lX;Xz3 NԾu߬-gFJcͰ\`]mrfsW9q$}% >zoVq笌G]e0N(eTκGBwx62 .ܻ`gR4"A~'V"Wc x#-h%~iىj`Kr,Nm)e>wgj~jζ[es#%`PjrVdC9 R\ܠuX۾\*CFwk"xwԏ$n658Ws~+<`Hml#cTp3OQh” 1ՙkW; p󃵢c@Bc( g߬Hj~n6Y8LEMx#I}*=̽x.0ݗھ@4mrGmwAh@dQ,Z&Ɖ[ԏD~̈+ 01R1~V iD#md,>@X (@`Rs.Z&Mv P3Mf73sF']m_ïՊbV&1isNc=H[c ^ýy慀-s*{ʊo#b3w#  @`]_+Ic1 $q] wΤ,1“7#'rlOeIirOEj:uVc fxhwj%4ToA0ET:p&̰n1zP'EskcKjr]w'w:1᫪62V!C NC"-N`d4N }}?U+aC5as*zH J=G~r,@p|]<͍BPBry!%j~$$t$0sVmOÕ Ћ{/ 7XIUG)^̽W Lg`g1˓ 6L2fj@JJYn%⤄-oͽ4^ !@* ʴ*_C Z:4g+|Bi_s&yyuƌ _ڕwJ Ab8)װS:2p4FLJFq @:ܽe !`@xmk~r>@m)=CS'Cu rZ=>HmR;j{fTr%Z@moI{g.~vx|+;D4\\U,\W U`@`LtW ቹC &8NIg%}|x.N IDAT"xO(ePUx.`0 0,aSK`B Q~d6Cs/2/F "ڋ`,n_Hl0 G !@wkLJfRM|Vsg3w=s,@"A"Ƕe9"Txd{xho0B%6#]L9L(ӁhLmdϞltfo 9r]ETC@@p<3n\YC;{/}gq=JfvyjD5N<¹xP '>~N X8E hᖳMyp $ڮv~:¯1  @`K}˄3!/c e§+OFj4g@d`ltnD43f7䖂vs.`0 0)LN2;I~&,1@4f~pn9Kȩ楼]3u޽-n9W讫8Fm0B,&N.di{94S 7~ ~3:# ׋p]`@`"?L+BC+ۿ|""hI OxQB)̃sʅ\%^6HBI$tqd`l \p2-  @{ %y.t6kPbO{"U.)pg:*.;!ho0B\&ZD J9!".@qHő:2du5T/G\qv!  ϘubxX9Z5&t@{gI䒻wWJf!@ ?"y@rfcg'4-$62aa es<}UDH{'Mg!"w#B`f _A+ +jV )a3 0<SL4 JAC% &p ZQ0XPcTBȣ; 봷  !@ ""u|Y(@ @>_A+  JJ@ev! @p  @1b~hj KsȐ% $ Ɓ%AB1{U5rhë]`@o)@`^Q9!Ksj_+  J]%2w)a@@"DP\ Kt6 lɽI1  ~2ӾEIL1U8G{no,A3`:s\?h J5oY@ B뽪vqWa]`@  09֨2i]Y9>Qn @=P 5 P  C4@ "25)O~C wAs7|NFB=@՜V5>X" @p #lqW'@<}E`6/?+ Cyu'v! @pߣ{` S _?Mh)5.Ff?$@1Hiu  !@ ކ C`[޲F`lZO=>ݐ9UG7N*Cm0B1cF\@`8B`JS(q lp~?wP sȇSCD\@JJ@u$0j hl,,nEcc]V~ ;8sJ٫\)A]E  !@ {&. Bj %!0d dFm_S0tTB:+ W|*N*,  !@ {*) Hj"(.{D`Bsk{$}}}˻P hhIpp]`@' ť tk\9!'Es0˄xwSO],h jB&z @@pK͢.ѭV?;^Dm_4WS(S_CbWav! #~WC b545 L{}S}]@]v\C@8M42n Gj7ZQ?M!X4wj+nhL'@~el]`@9sq l,Vqc 0@hv\ $aG / H,@,1 l@>4|v_v?ir-y}]jlz3B*]`@3q =֫z3[p]?h3il?L믠 5B5!S"Zv! @pgǝ`ZFEk?p J+ P|*:E.`0 w>n PR$~59I L|_Ayr]xA,,1 /ܽd ?!)YLjAz}͝ܫ 3ZAATE{ܵ@q,Y#`>Uv 05 2S C@(@DL@A%k tT?M!ehmHF8vG?cBpZ !q^-uP  P{X PKJ`'1~5IE ^zj @I'd@d 9䱋irTP .pv x^-uio0B@1Pb@ma]3Ԟ_?h@Fq42tOy@m0B@I~cJ\.wmYNN@~AL8!!`~~} U.oA*mr%0O| b֓Yކlhp%D`@ P f:$ N@>}S0jj\:^<|\~cDц@St;MnOGQ!CD/`:}ݝ\?;;M+ǯvrg=>p'U׳,0 {u.`0 3"VWW _)4w+v3cը. \EvH*2!@6ڄue}%% 9gym˹WA0 T!@{2$G@Z"=\Q;[+JzJt)_+;@G[^OgYD6l9oC@hDh{߸BܭZa}}}?; m/~h ~Dc,QEpGm'>~ %x6YsjmYNU?<}2ؠu2:W!6!/6s1  @:ܭe 7hߦf5Ι,rC[w;ͨ }kS j?t%1:C@@731 0$%U m *!4-[gkhg5`*T^)T]ϲmXi.-j@AK &+-_)H PHwھS~Vk f#@q=l;jH>C@{ϘubWg%q3fO,k-4 X(!~4V Ҥ{j`/1ΖY t!@{L @Y}K͋lg (&t-G(K(l2Gࣸ-ý,^ϲmXc.6r  %n658H`l bƚ.~塀RtWSB9 P?]T;jBEwߗer;kESvVshc}lv4tf PBfSSñ^f 6!@hI @a[ d2'_^n~+;n!]j߆Q?[#%4U3QJp'׳,|H8o;Q9 !n$@ ߾>c[V p~ۧݠھB8[? P?t EpY<,;j{̗c@pBIp@./El. AsoYéYb3U%"Υh\0 - d* ,01Aso|R^?.XP?SUBJ? " \)s<z>B(G`Y >+,Kul9$Xc'S?=~8?~r~&3p/7|꺖t$pOT}%@t M g{#7gW\ )ybG/dL8>~rLKh|'ߪ'~g~/i{CP  @ S@ 1xR@0}2/JE+8tugݧ9ɨz亖NOv$mO<}%@>}, 0}z;ېyɥ,bӹ+OUO> wKK Њ3[q @S؟B3#Boc鿛zXB29+Xg "` [ Ъ-VqOϟK|^pue^ ;O sQݝ+F\$@P IDAT[q @))Q_Kϯ}y[]IWp"@ @`=t 0r.m/`e1lj7& npm:"@|rʘ繋˭?[z%S24PO3z%@&poLIw CM>aWj۷~&A @II@ @(#x{keeRm]vdT^wi{+ @Yz'@D oлEE7Ħnzs5m_tp{e5)C 9@;TsZ&yVbp^c! @*ý¢t"p Of].m|] vp}pf0 P¢) =e פش¢44$맒bI]b{r" б. BoG.mS*.M(C~/}*}p86SN)Օ$o@.m+cO6M+@[?p '*T5$L j: Е-53ssw}^l H%sٽw;/J/jvgW_1& w! pfACk+ss%?i'++mtij\c#@" o7tpSbӛ;C%m_tp 6MS h_.a:6tvM*YUC~* @ ke͋K@K{u|jϥ]p5qI@S5ͅl7ޮ3sgKM[V}c~.[F~IoMۯ33~Jo:c  c  @I'{h?*m?d{_l{soEMJ 8R @MbMDo..m_\.9OH濶vw$mS4 @(Q*m @Z|u\ĦרZ?9չ:p_sFJ_|FO @Pm @{d=_u{5+Sqq^$ @og]_#@ ?>9zx׮Cy1[~Ƭ{Y[?$@k믬e{: 0#W  @@wܧ}^6ĕ|'ʓsgnr|>W%m? u[@w}͎ !P~>s_Iۧ& X?м2 X?EG.mk3[yhFpDž{>~y>~^B >R!@^ke͋ - @y4qWD"}~<9~&]wVP!@+pBU @@"dJ"89@Rk|@:I`iy@V+8n @ܖgf'l#g^χ,t^ ce6K~f  @?{55# @uf^0m ̼p^:o8r ȫ  @3ܭ @@8d롃#hHCV^K{-3 g۶WV!@po`K)g_z_/M\~<9d\}yK]IrL)X @ A m @A=nLo _$ksA Mu~z ܥ2: п@f@_MeBk. ~y>x4R  @4 @ cc>_$`\;B.{_6loZ Ыʚ PX| +a""oKG3GBj @`SWX#h^K"vsZO} /, `U6G @_=-e^j%-̥kֵkk}n=,u^}~  @` 6M @@={<{h]t+{6r ܥԘ<. p@4)PgP+a))mn @@=pFB$pyh"¯cz*.J7VΘ#s[}moZG з} @ks?vI}{ql1ӱk^`,R; D QH4 @ h=sO#O@|TR4K.B @ [ @m{}+UJLSPeS$psp5@8%aguַShf$_x5秣WecF @$`D @@}lr_Gٛ[MF"B^ihتXE!0l_TWj͜9OF~# pfJ\. s&T 35Tlܥ5Xc @.]դ @d"M"Lx|_5s2pef @`EM]q [£cߓ?%N{iX39ض(y @keTD;'͋>ְsqfԚ|}3%@G_@^R}Tڇޘ͜ ܯ>OF>OY @;E $P6pps_Y+"An4o^0^y @g(y [aɲ_p |6U:^31 j{5K @_8|!@(& c7o^mYܰ`p<i  @N"@F?^u%a77B \`* ܥ-- @5@\@~9D;yn*) Ч3Y @U 8Ͻr8KINmdloa=##`{=0 @s~Zjڿn eW˱ג @ @' sϺhO02zQR]R4lhZ@t hO@^͖F|>/^uTz[@r p%`  @଀3 z @`@Cu ӺO Tlo?N ~[ @6kM[૭U- ^*{w_]^p%h0 @̽cS*,Y ほ  N@Nu @2k}o]^Cd5 nػN7Hw @*gWU!@הcjHNWF9:~Fq!7Kh.=ؽy2͉Zýj+ ПCd?gN|hH`o>}n@Rٵýl.mO+_@~  @_d_8|y/ oSNNZ'4.T ܥ/+'@N4 @dM/_R/T n @`SD @ P젽Sz݋=آ@mW1 @@vwYV"@ɽU?|BN_3qP@^aQ T@>hovk[ L$3p/ue Жz- й̽W6=Lqꮷ<d ̽HY?I;() @ TKZN^^"*6.{1J  @6mͨ  @܅/^.voxym -u  @@EhjE0 @@@ٿ*R1L[kWX˞B勇Jy2R!@~^ @%nr%Ǫ- Tnȩp?KO @ $@&O#xg1$poO  z )yv/T.v`i mo@rឣ @@esqD!9K{#c;حdj1 @ ;3??6?D@$7^_a}ĕ";ܥM  @}E @@K2e \ݛ[/d݀  @$ p(| @4, soxޣ@]9K* @3& @p{{534Ob&[  @u;[ @>swO؛G$pe=<i{ 4 pR@~ @a2ji( ]0  p @] ܻ* , oF g @3KIj Pܫ(A8-p8} )4˲dlo- PVƸ @'sw xO߾=%@ k @ ܏y@"j7pP5c @15EK@^W={-=# @%gT-c%@p..L)T^}  @ 0*mt:³;ܥ#s$@ݔ  @dÕ܄ G z<}kw}mGZpL3v @ߦ}!Pqq'@@OQ=_m Ps @@ct @s/  %{x|/+ @GtSJ!@lIYغi{x&|7OG ak>ǒ}I5 ЏZ  @`S d\ Ї@qdؼzK7 @@+h @}q&sgi ܙ/,>y^YP @w}͎ 8=@IfywI8,> @^pﵲEX3}s_3u@MIڞ5bM_UzhG г @`E@澂v=L}4s}1p3 @{u4  @8زwT&zھK;#xf9<@p{5; @FH6)ȁ=CGw;WOjjF @qte8I ВKر FqX=EZ\=c'@*{ QmU Iۇؼx&#u5|&@ ;. @r|J枫9Hs^>s % m @er ~/7* $Q{wݻד=?5 `޾+ @@7vwSJ!@C=~>@I.=w)OК#hT@h  p@:/!(ӄ nM@[E͇ pR Nd'1N@eߵ "X@qqM pP dF@KrKh/Gk Nu з?w}͎ @UUqDUM` @pV @&ki $Q{yzmC5= $Q{  б#e:. @L{h.'sQf Z!0@) @8R" @Z8wơ|{u^ɏLӇ @~;믑 @Ͻ2DI&*mfFt+ nKkb @"V g @D2  @q`e#W b @FGXc"@T+`jKc`- $Q{Bgd @/'@&%$m/!FhL2p  @ ı`2T( m(D8/`yC- @Fص=˜ LIǿĚ @E{U3f @@2*`M $i, @`[2F @XxOHV~M 0j @Bb8s{J{H0i{4t @{;۸C PT cѮ4F"d]^Qy  PTE95FR`f3"$Q{ @`QE  @vb/6'8r (0_OJ32f @M& @$q3snH4>9'@x+H4n @?[&,5 $Q{z @ @ G_c?IEc/'@Fp̈U7g @uqoc0+M Hۛ. @|/ҊF @L!s=֧g| Г@ſyiB .Huw  @ L{x%sw;HvQ9 @aG<\ @$\H$ @:;ܯ2 @`tM>JO0i{e4d @@a; j @`8R@Iza @7 @[8\,VЍi{}c4" @g)^  @C d-'P+&Q{v+Vfa @ ܯ2 @§6]f p#e: @K N'쉏e_+WR @T. @@7!s=ֻ'Dao @p= @2=0^('rZ"@)H>jV @ >I<%=i{[3Z @#v?®S @9}CG'Da,G s @@3v7S*%@t#gqMi$m5^MO  @_-} @8|;XfΥij @+GtUN!@%sLmշ&Q{|kb @O ܟ/ @dA I.j(@hQ2-V͘  @fIKd @fpoTJX`s{;XpԒ= @{E4 @@2]T  P @`4=>ڪhIz_DY @hpKT N<`VfKg @poHHG`s{;}QjIT!@Y@>r͝ P̽ƪ?$j_U3 @T' p$D s $i, @&gO> @"287U.%@-`{3z @@ a/3SKT3-{ @}E @*dUA$i25 @1G4V0%@ %'F~iHd= iH&@IM @ $Q{P~ @p@ PejP5KvQ{51 @(IhW M,nja @l I @*s U"_T9d"@S>jV @uϽQ@u/F&Dazjl> @ ͐  @ _I.j|=> @GS #!@xW{kY@r й @ keWDW/+/E @=z!@(/ s/oZqI.jVFW2  @ đkr䒠O$EK$ @O  @=dqǚYKZfKhJ2M`  @=܌3xKoV@. @|sE@ м@!{'ms`I*0{S$@hR&f @s&^I0 i{4l @h*n @dT7IE @8RfZ) @8ϖPK%̓ 0޴  @ he="I2l @U|  @ ܛqڛ @V @@2VDaVjg @sgM\!@h^ m^m9Ֆ @ ~[ @ ,s?ECLw$Nټ @w_b$@ - sIF(mLD p@@~+ @- ܫVګ @pI@ @.Gy2I{9 @| i @@6?^$j  @@{F @. sB~rK @T @Vd$jA>O) @d 1 @L9N"T=2i/ @M& @Fx,'t4L߁t,`j @ 8Rf  @ϖ2I&.m3 @ww2 @h{ @[y @`PA2$jŖM @਀3܏y @`rgi0D  @.B\M @$Ͻܓ}_02ͅ @{=0 @ܓ=@^*4> @)Sqq  @yf¨5d!jR;$@S@^g] @Fy^1 @hJ2M`  @*h`$j="b @ ǭ @h7sOvQ5E @sGM\!@ !S'YFONF8'ƥO @|dA @^V/sw%Q{xMڞk9 @ɦ  @/O @Xx0㖶- @~^ @H۵sOكS: @u @-sOvQ{Ũ  @:pL'4  @;K 5i6T/!@ @ S{s#@ @`@}^_8u1w  @ ` Ⱥ @ mFI Q0  @ @/I_4t݌  @(/7" @X 9X;>'Q{xSھϣ @zc= @-P$OffǮ @(,`{aP @XDqQ @ @qJ>̟y @<"`#:%@T`>$mhL @ H  @"DaQjo @- r @AMI.joȆL 03-i @<%I\~j%@ @ SL( @()0D3i{Iqm @^ @@ڷ'@ P5VŘ @F7> @hH P  @X /v5m!@ Я @Z[(1 @X}M= @ @ )`{& @ @ @k: @ @ @L{& @ @ @}M= @ @ ) pτ @ @XG @ @2P#@ @ @k5 @ @ @@=c @ @ @`M@ྦ @ @gBy @ @ t#@ @ @L( @ @ @5{ @ @ @ S@ 1 @ @ & p_q @ @d 3 #include #include namespace { complex_t sinc(const complex_t z) // cardinal sine function, sin(x)/x { if (z == complex_t(0., 0.)) return 1.0; return std::sin(z) / z; } std::vector base_vertices(double length, double width) { const double a = length / 2; const double b = width / 2; return {{+a, +b, 0}, {+a, -b, 0}, {-a, -b, 0}, {-a, +b, 0}}; } } // namespace ff::Box::Box(double length, double width, double height, const R3& location) : Prism(true, height, base_vertices(length, width), location), m_length(length), m_width(width) { } ff::Box::~Box() = default; complex_t ff::Box::formfactor_at_center(const C3& q) const { complex_t qzHdiv2 = m_height / 2 * q.z(); return m_length * m_width * m_height * sinc(m_length / 2 * q.x()) * sinc(m_width / 2 * q.y()) * sinc(qzHdiv2); } libformfactor-v0.3.2-7e235d29b1ab60f2e4788e7c819d7c0277aa2954/ff/Box.h000066400000000000000000000020301477075301700233500ustar00rootroot00000000000000// ************************************************************************************************ // // libformfactor: efficient and accurate computation of scattering form factors // //! @file ff/Box.h //! @brief Defines class Box. //! //! @homepage https://jugit.fz-juelich.de/mlz/libformfactor //! @license GNU General Public License v3 or higher (see LICENSE) //! @copyright Forschungszentrum Jülich GmbH 2022 //! @author Joachim Wuttke, Scientific Computing Group at MLZ (see CITATION) // // ************************************************************************************************ #ifndef FF_BOX_H #define FF_BOX_H #include "ff/Prism.h" namespace ff { class Face; class Polyhedron; class Box : public Prism { public: Box(double length, double width, double height, const R3& location = {}); ~Box() override; complex_t formfactor_at_center(const C3& q) const override; private: // given geometry: const double m_length; const double m_width; }; } // namespace ff #endif // FF_BOX_H libformfactor-v0.3.2-7e235d29b1ab60f2e4788e7c819d7c0277aa2954/ff/CMakeLists.txt000066400000000000000000000013371477075301700252200ustar00rootroot00000000000000set(lib formfactor) set(${lib}_LIBRARY ${lib} PARENT_SCOPE) file(GLOB src_files *.cpp) set(api_files Box.h Face.h IBody.h Make.h Polyhedron.h Prism.h Topology.h ) add_library(${lib} ${src_files}) target_include_directories(${lib} PUBLIC "$" "$" ) target_include_directories(${lib} PUBLIC "${LibHeinz_INCLUDE_DIR}") set_target_properties( ${lib} PROPERTIES OUTPUT_NAME ${lib} VERSION ${PROJECT_VERSION}) install( TARGETS ${lib} EXPORT formfactorTargets LIBRARY DESTINATION lib RUNTIME DESTINATION lib ARCHIVE DESTINATION lib) install( FILES ${api_files} DESTINATION include/ff) libformfactor-v0.3.2-7e235d29b1ab60f2e4788e7c819d7c0277aa2954/ff/Edge.cpp000066400000000000000000000047421477075301700240330ustar00rootroot00000000000000// ************************************************************************************************ // // libformfactor: efficient and accurate computation of scattering form factors // //! @file ff/Edge.cpp //! @brief Implements class Edge //! //! @homepage https://jugit.fz-juelich.de/mlz/libformfactor //! @license GNU General Public License v3 or higher (see LICENSE) //! @copyright Forschungszentrum Jülich GmbH 2022 //! @author Joachim Wuttke, Scientific Computing Group at MLZ (see CITATION) // // ************************************************************************************************ #include "ff/Edge.h" #include "ff/Factorial.h" #include #include #include namespace { constexpr auto ReciprocalFactorialArray = ff_aux::generateReciprocalFactorialArray<171>(); } // namespace ff::Edge::Edge(R3 Vlow, R3 Vhig) : m_E((Vhig - Vlow) / 2), m_R((Vhig + Vlow) / 2) {} //! Returns sum_l=0^M/2 u^2l v^(M-2l) / (2l+1)!(M-2l)! - vperp^M/M! complex_t ff::Edge::contrib(int M, C3 qpa, complex_t qrperp) const { complex_t u = qE(qpa); complex_t v2 = m_R.dot(qpa); complex_t v1 = qrperp; complex_t v = v2 + v1; // std::cout << std::scientific << std::showpos << std::setprecision(16) << "contrib: u=" << u // << " v1=" << v1 << " v2=" << v2 << "\n"; if (v == 0.) { // only 2l=M contributes if (M & 1) // M is odd return 0.; return ReciprocalFactorialArray[M] * (pow(u, M) / (M + 1.) - pow(v1, M)); } complex_t result = 0; // the l=0 term, minus (qperp.R)^M, which cancels under the sum over E*contrib() if (v1 == 0.) result = ReciprocalFactorialArray[M] * pow(v2, M); else if (v2 == 0.) { ; // leave result=0 } else { // binomial expansion for (int mm = 1; mm <= M; ++mm) { complex_t term = ReciprocalFactorialArray[mm] * ReciprocalFactorialArray[M - mm] * pow(v2, mm) * pow(v1, M - mm); result += term; // std::cout << "contrib mm=" << mm << " t=" << term << " s=" << result << "\n"; } } if (u == 0.) return result; for (int l = 1; l <= M / 2; ++l) { complex_t term = ReciprocalFactorialArray[M - 2 * l] * ReciprocalFactorialArray[2 * l + 1] * pow(u, 2 * l) * pow(v, M - 2 * l); result += term; // std::cout << "contrib l=" << l << " t=" << term << " s=" << result << "\n"; } return result; } libformfactor-v0.3.2-7e235d29b1ab60f2e4788e7c819d7c0277aa2954/ff/Edge.h000066400000000000000000000023431477075301700234730ustar00rootroot00000000000000// ************************************************************************************************ // // libformfactor: efficient and accurate computation of scattering form factors // //! @file ff/Edge.h //! @brief Defines class Edge //! //! @homepage https://jugit.fz-juelich.de/mlz/libformfactor //! @license GNU General Public License v3 or higher (see LICENSE) //! @copyright Forschungszentrum Jülich GmbH 2022 //! @author Joachim Wuttke, Scientific Computing Group at MLZ (see CITATION) // // ************************************************************************************************ #ifndef FF_EDGE_H #define FF_EDGE_H #include #include #include namespace ff { //! One edge of a polygon, for form factor computation. class Edge { public: Edge(R3 Vlow, R3 Vhig); R3 E() const { return m_E; } R3 R() const { return m_R; } complex_t qE(C3 q) const { return m_E.dot(q); } complex_t qR(C3 q) const { return m_R.dot(q); } complex_t contrib(int m, C3 qpa, complex_t qrperp) const; private: R3 m_E; //!< vector pointing from mid of edge to upper vertex R3 m_R; //!< position vector of edge midpoint }; } // namespace ff #endif // FF_EDGE_H libformfactor-v0.3.2-7e235d29b1ab60f2e4788e7c819d7c0277aa2954/ff/EulerOperations.cpp000066400000000000000000000470761477075301700263160ustar00rootroot00000000000000// ************************************************************************************************ // // libformfactor: efficient and accurate computation of scattering form factors // //! @file ff/EulerOperations.cpp //! @brief Implements atomic operations //! //! @homepage https://jugit.fz-juelich.de/mlz/libformfactor //! @license GNU General Public License v3 or higher (see LICENSE) //! @copyright Forschungszentrum Jülich GmbH 2022 //! @author Joachim Wuttke, Scientific Computing Group at MLZ (see CITATION) // // ************************************************************************************************ #include "ff/EulerOperations.h" #include "ff/Edge.h" #include "ff/Face.h" #include "ff/Topology.h" #include #include #include namespace { //! Returns a pair std::pair //! - bool: a flag, it stores if the line is intersected. //! - R3: the point of intersection. //! This function is used for calculating the intersection of a plane with the offset //! z_cut from the xy-plane with a line segment given by the vertices (v0,v1). std::pair edge_intersect(const R3& v0, const R3& v1, double z_cut) { if (z_cut <= std::min(v0.z(), v1.z()) || z_cut >= std::max(v0.z(), v1.z()) || v0.z() == v1.z()) return {false, R3()}; R3 interpolated = (z_cut - v0.z()) / (v1.z() - v0.z()) * v1 + (v1.z() - z_cut) / (v1.z() - v0.z()) * v0; // only use x,y since z may != z_cut due to numerical imprecision return {true, {interpolated.x(), interpolated.y(), z_cut}}; } //! Returns true if the xy-plane with the z_cut offset intersects with polyhedron. bool assert_plane_cut(const ff::Polyhedron& polyhedron, double z_cut) { bool above = false; bool below = false; for (const R3& v : polyhedron.vertices()) { if (v.z() > z_cut) above = true; if (v.z() < z_cut) below = true; } return above && below; } //! This function collects information about all edges that intersect with the clipping plane //! Returns a vector of tuples: //! - int: the vertex index in ff::FacialTopology::vertexIndices. //! - int: the vertex index in ff::FacialTopology::vertexIndices. //! - R3: the point of the intersection. std::vector> findIntersectingEdges(const ff::Polyhedron& polyhedron, double z_cut) { const std::vector& faces = polyhedron.topology().faces; const std::vector& vertices = polyhedron.vertices(); std::vector> result; for (size_t iF = 0; iF < faces.size(); iF++) { std::vector> edges = faces[iF].edges_directed(); // since mev creates a new edge edge_offset corrects the wrong indices for (size_t iE = 0; iE < edges.size(); iE++) { const auto [iE0, iE1] = edges[iE]; if (const auto& [ok, intersection] = edge_intersect(vertices[iE0], vertices[iE1], z_cut); ok) { auto itr = std::find_if(result.begin(), result.end(), [iE0 = iE0, iE1 = iE1](auto tup) { return (std::get<0>(tup) == iE0 && std::get<1>(tup) == iE1) || (std::get<0>(tup) == iE1 && std::get<1>(tup) == iE0); }); if (itr == result.end()) result.emplace_back(iE0, iE1, intersection); } } } return result; } //! This function collects information about all faces that intersect with the clipping plane //! Returns a vector of tuple: //! - bool: a flag, it stores if a face was found. //! - int: the face index in ff::Topology::faces. //! - int: the edge index in ff::FacialTopology::vertexIndices of the first vertex intersecting. //! - int: the edge index in ff::FacialTopology::vertexIndices of the second vertex intersecting. std::tuple findIntersectingFaces(const ff::Polyhedron& polyhedron, double z_cut) { const std::vector& faces = polyhedron.topology().faces; const std::vector& vertices = polyhedron.vertices(); for (size_t iF = 0; iF < faces.size(); iF++) { std::vector indices; const std::vector& vertexIndices = faces[iF].vertexIndices; const int SZ = vertexIndices.size(); if (SZ <= 3) continue; // collect vertex indices of vertices in z plane for (int iE = 0; iE < SZ; iE++) { if (vertices[vertexIndices[iE]].z() == z_cut) indices.push_back(iE); } if (indices.size() >= 2) { // check if edge exists already while (!indices.empty() && ((indices[0] + 1) % SZ == indices[1] || ((indices[0] - 1) % SZ + SZ) % SZ == indices[1])) { indices.erase(indices.begin()); indices.erase(indices.begin()); } if (indices.empty()) continue; return {true, iF, indices[0], indices[1]}; } } return {false, -1, -1, -1}; } //! This function collects information about all edges starting from z_cut to some location //! below/above z_cut Returns a vector of tuple: //! - bool: a flag, it stores if an edge was found. //! - int: the vertex index in ff::Polyhedron::vertices of the vertex with .z() == z_cut. //! - int: the vertex index in ff::Polyhedron::vertices of the vertex with .z() < z_cut. std::tuple findRemovableEdges(const ff::Polyhedron& polyhedron, double z_cut, bool findAbove) { const std::vector& faces = polyhedron.topology().faces; const std::vector& vertices = polyhedron.vertices(); for (const ff::FacialTopology& ft : faces) { for (auto& [v0, v1] : ft.edges_directed()) { if (vertices[v0].z() == z_cut && vertices[v1].z() == z_cut) continue; if (findAbove) { if (vertices[v0].z() == z_cut && vertices[v1].z() > z_cut) return {true, v0, v1}; if (vertices[v1].z() == z_cut && vertices[v0].z() > z_cut) return {true, v1, v0}; } else { if (vertices[v0].z() == z_cut && vertices[v1].z() < z_cut) return {true, v0, v1}; if (vertices[v1].z() == z_cut && vertices[v0].z() < z_cut) return {true, v1, v0}; } } } return {false, -1, -1}; } //! Returns a vector of face indices, where each face has all vertices located in the offset //! z_cut xy-plane. std::vector faces_in_plane(const ff::Polyhedron& polyhedron, double z_cut) { const std::vector& faces = polyhedron.topology().faces; const std::vector& vertices = polyhedron.vertices(); std::vector result; for (auto ft = faces.begin(); ft != faces.end(); ft++) { bool allOnPlane = true; for (const int edge : ft->vertexIndices) { if (vertices[edge].z() != z_cut) allOnPlane = false; } if (allOnPlane) result.push_back(std::distance(faces.begin(), ft)); } return result; } //! This method us used to find a common edge of two faces given by their indices iF0,iF1 in //! ff::Topology::Faces. //! Returns a tuple: //! - bool: a flag, it stores if a common edge has been found. //! - int: a vertex index of ff::Polyhedron::vertices //! - int: a vertex index of ff::Polyhedron::vertices std::tuple common_edge(const ff::Polyhedron& polyhedron, int iF0, int iF1) { for (auto eF0 : polyhedron.topology().faces[iF0].edges_undirected()) { for (auto eF1 : polyhedron.topology().faces[iF1].edges_undirected()) { if (eF0 == eF1) return {true, eF0.first, eF0.second}; } } return {false, -1, -1}; } //! This function collects information about faces, with all vertices .z() == z_cut, that share an //! edge. Returns a vector of tuple: //! - bool: a flag, it stores if two faces were found. //! - int: the face index in ff::Topology::faces //! - int: the face index in ff::Topology::faces. std::tuple findMergeableFaces(const ff::Polyhedron& polyhedron, double z_cut) { const std::vector faces = faces_in_plane(polyhedron, z_cut); for (int iF0 : faces) { for (int iF1 : faces) { if (iF0 == iF1) continue; if (const auto& [ok, iV0, iV1] = common_edge(polyhedron, iF0, iF1); ok) return {true, iV0, iV1}; } } return {false, -1, -1}; } //! Returns a pair of Topology and vector of vertices. //! - Duplicate vertex ids in ff::FacialTopology::vertexIndices get removed. //! - ff::Topology::faces erases ff::FacialTopology's with less than 3 vertices in //! ff::FacialTopology::vertexIndices. //! - All unused vertices of ff::Polyhedron::vertices get //! removed and all vertices left get a new index assigned. std::pair> strip(const ff::Topology& topology, const std::vector& vertices) { // clear faces ff::Topology newTopology; newTopology.symmetry_Ci = false; for (auto ft = topology.faces.begin(); ft != topology.faces.end(); ft++) { std::vector vertexIndices; for (auto iV = ft->vertexIndices.begin(); iV != ft->vertexIndices.end(); iV++) { if (iV == ft->vertexIndices.end() - 1) { if (*iV == *ft->vertexIndices.begin()) continue; } else if (*iV == *(iV + 1)) continue; vertexIndices.push_back(*iV); } if (vertexIndices.size() < 3) continue; newTopology.faces.push_back({vertexIndices, false}); } // creating the new vertex mapping by collecting all used vertices std::unordered_map id_mapper; int next_new_id = 0; for (ff::FacialTopology& ft : newTopology.faces) { // rotate vertexIds vector to start with min element to stay as close as possible to old ids auto itr = std::min_element(ft.vertexIndices.begin(), ft.vertexIndices.end()); std::rotate(ft.vertexIndices.begin(), itr, ft.vertexIndices.end()); for (const int vertex_id : ft.vertexIndices) { if (id_mapper.count(vertex_id) == 0) id_mapper[vertex_id] = next_new_id++; } } // building the new vertex vector std::vector newVertices(id_mapper.size()); for (const auto& [old_id, new_id] : id_mapper) newVertices[new_id] = vertices[old_id]; // mapping to the new vertex ids for (ff::FacialTopology& ft : newTopology.faces) { for (int& vertex_id : ft.vertexIndices) vertex_id = id_mapper[vertex_id]; } return {newTopology, newVertices}; } } // namespace //! Returns a new Polyhedron with modified faces. The face at index iF is divided along //! a new edge (iE0,iE1) into two new faces. The old face gets replaced by //! the first new face. The second new face gets pushed back in the face vector of Topology. ff::Polyhedron* ff::atomic::mef(const Polyhedron& polyhedron, int iF, int iE0, int iE1) { if (iF < 0 || iF >= (int)polyhedron.topology().faces.size()) throw std::runtime_error("Invalid call to libformfactor, function mef: " "iF out of range"); if (iE0 < 0 || iE0 >= (int)polyhedron.topology().faces[iF].vertexIndices.size()) throw std::runtime_error("Invalid call to libformfactor, function mef: " "iE0 out of range"); if (iE1 < 0 || iE1 >= (int)polyhedron.topology().faces[iF].vertexIndices.size()) throw std::runtime_error("Invalid call to libformfactor, function mef: " "iE1 out of range"); if (iE1 < iE0) std::swap(iE0, iE1); if (iE1 - iE0 <= 1) throw std::runtime_error("Invalid call to libformfactor, function mef: " "cannot make edge from indices"); const std::vector& vertexIndices = polyhedron.topology().faces[iF].vertexIndices; // create new edge loops std::vector vI0; std::vector vI1; bool left = true; for (size_t i = 0; i < vertexIndices.size(); i++) { const int iV = vertexIndices[i]; if (left) { if ((int)i == iE0) { vI0.push_back(iV); vI1.push_back(iV); left = false; } else { vI0.push_back(iV); } } else { if ((int)i == iE1) { vI0.push_back(iV); vI1.push_back(iV); left = true; } else { vI1.push_back(iV); } } } Topology newTopology(polyhedron.topology()); newTopology.symmetry_Ci = false; newTopology.faces[iF] = {vI1, false}; newTopology.faces.push_back({vI0, false}); return new Polyhedron(newTopology, polyhedron.vertices()); } //! Returns a new Polyhedron with a the Vertex v will be //! inserted in all edges between. ff::Polyhedron* ff::atomic::mev(const Polyhedron& polyhedron, int iV0, int iV1, const R3& v) { if (iV0 < 0 || iV0 >= (int)polyhedron.vertices().size()) throw std::runtime_error("Invalid call to libformfactor, function mev: iV0 out of range"); if (iV1 < 0 || iV1 >= (int)polyhedron.vertices().size()) throw std::runtime_error("Invalid call to libformfactor, function mev: iV1 out of range"); if (iV0 == iV1) throw std::runtime_error( "Invalid call to libformfactor, function mev: iV0 and iV1 are equal"); std::vector newVertices(polyhedron.vertices()); // find or insert new vertex int iV = -1; auto itrV = std::find(newVertices.begin(), newVertices.end(), v); if (itrV == newVertices.end()) { newVertices.push_back(v); iV = newVertices.size() - 1; } else { iV = std::distance(newVertices.begin(), itrV); } if(iV == iV0 || iV == iV1) throw std::runtime_error("Invalid call to libformfactor, function mev: " "new vertex is already part of the old edge"); Topology newTopology(polyhedron.topology()); newTopology.symmetry_Ci = false; for (FacialTopology& ft : newTopology.faces) { const std::vector> edges = ft.edges_undirected(); for (size_t iE = 0; iE < edges.size(); iE++) { auto& [iv0, iv1] = edges[iE]; if ((iv0 == iV0 && iv1 == iV1) || (iv0 == iV1 && iv1 == iV0)) { ft.vertexIndices.insert(ft.vertexIndices.begin() + iE + 1, iV); ft.symmetry_S2 = false; break; } } } return new Polyhedron(newTopology, newVertices); } //! KEF is an Euler Operator used to Kill one Edge one Face. The edge is given by the its vertex //! identifiers iV0, iV1 which are the indices of ff::Polyhedron::vertices. ff::Polyhedron* ff::atomic::kef(const Polyhedron& polyhedron, int iV0, int iV1) { if (iV0 < 0 || iV0 >= (int)polyhedron.vertices().size()) throw std::runtime_error("Invalid call to libformfactor, function kef: iV0 out of range"); if (iV1 < 0 || iV1 >= (int)polyhedron.vertices().size()) throw std::runtime_error("Invalid call to libformfactor, function kef: iV1 out of range"); // find edge and prepare new topology int iF0 = -1; int iE0 = -1; int iF1 = -1; int iE1 = -1; Topology result; result.symmetry_Ci = false; const std::vector& faces = polyhedron.topology().faces; for (size_t iF = 0; iF < faces.size(); iF++) { const std::vector& vertexIndices = polyhedron.topology().faces[iF].vertexIndices; const int NVI = vertexIndices.size(); bool found = false; for (int iE = 0; iE < NVI; iE++) { if (vertexIndices[iE] == iV0 && vertexIndices[(iE + 1) % NVI] == iV1) { iF0 = iF; iE0 = iE; found = true; } else if (vertexIndices[iE] == iV1 && vertexIndices[(iE + 1) % NVI] == iV0) { iF1 = iF; iE1 = iE; found = true; } } if (!found) result.faces.push_back(faces[iF]); } if (iF0 == -1 || iF1 == -1 || iE0 == -1 || iE1 == -1) throw std::runtime_error("Bug in libformfactor: in function kef, E or F not found"); if (iF0 == iF1) throw std::runtime_error("Bug in libformfactor: in function kef, F0 = F1"); // merge both edge loops into vF0 connected by iV0, iV1 std::vector vF0(faces[iF0].vertexIndices); std::vector vF1(faces[iF1].vertexIndices); std::rotate(vF0.begin(), vF0.begin() + iE0 + 1, vF0.end()); std::rotate(vF1.begin(), vF1.begin() + iE1 + 1, vF1.end()); vF0.insert(vF0.end(), vF1.begin() + 1, vF1.end() - 1); result.faces.push_back({vF0, false}); return new Polyhedron(result, polyhedron.vertices()); } //! Returns a new Polyhedron with modified faces: iV1 gets dissolved into iV0 ff::Polyhedron* ff::atomic::kev(const Polyhedron& polyhedron, int iV0, int iV1) { if (iV0 < 0 || iV0 >= (int)polyhedron.vertices().size()) throw std::runtime_error("Invalid call to libformfactor, function kev: iV0 out of range"); if (iV1 < 0 || iV1 >= (int)polyhedron.vertices().size()) throw std::runtime_error("Invalid call to libformfactor, function kev: iV0 out of range"); bool found = false; Topology tmp(polyhedron.topology()); tmp.symmetry_Ci = false; for (const FacialTopology& ft : tmp.faces) { for (auto& [iv0, iv1] : ft.edges_directed()) { if ((iv0 == iV1 && iv1 == iV1) || (iv0 == iV1 && iv1 == iV0)) found = true; } } if (!found) throw std::runtime_error( "Invalid call to libformfactor, function kev: edge does not exist"); for (FacialTopology& ft : tmp.faces) { for (int& iV : ft.vertexIndices) if (iV == iV1) iV = iV0; } const auto& [newTopology, newVertices] = strip(tmp, polyhedron.vertices()); return new Polyhedron(newTopology, newVertices); } //! Returns a new polyhedron with the volume below R3{0,0,z_cut} removed. ff::Polyhedron* ff::atomic::z_clip(const Polyhedron& polyhedron, double z_cut, bool keepBelow) { auto result = std::make_unique(polyhedron.topology(), polyhedron.relative_vertices()); z_cut = z_cut - polyhedron.location().z(); if (!assert_plane_cut(*result, z_cut)) return new ff::Polyhedron(result->topology(), result->relative_vertices(), polyhedron.location()); for (auto& [iE0, iE1, v] : findIntersectingEdges(*result, z_cut)) result.reset(mev(*result, iE0, iE1, v)); for (auto&& [ok, iF, iE0, iE1] = findIntersectingFaces(*result, z_cut); ok;) { result.reset(mef(*result, iF, iE0, iE1)); std::tie(ok, iF, iE0, iE1) = findIntersectingFaces(*result, z_cut); } for (auto&& [ok, iV0, iV1] = findRemovableEdges(*result, z_cut, keepBelow); ok;) { result.reset(kev(*result, iV0, iV1)); std::tie(ok, iV0, iV1) = findRemovableEdges(*result, z_cut, keepBelow); } for (auto&& [ok, iF0, iF1] = findMergeableFaces(*result, z_cut); ok;) { result.reset(kef(*result, iF0, iF1)); std::tie(ok, iF0, iF1) = findMergeableFaces(*result, z_cut); } result.reset( new ff::Polyhedron(result->topology(), result->relative_vertices(), polyhedron.location())); return result.release(); } libformfactor-v0.3.2-7e235d29b1ab60f2e4788e7c819d7c0277aa2954/ff/EulerOperations.h000066400000000000000000000030641477075301700257500ustar00rootroot00000000000000// ************************************************************************************************ // // libformfactor: efficient and accurate computation of scattering form factors // //! @file ff/EulerOperations.h //! @brief Defines atomic operations //! //! @homepage https://jugit.fz-juelich.de/mlz/libformfactor //! @license GNU General Public License v3 or higher (see LICENSE) //! @copyright Forschungszentrum Jülich GmbH 2022 //! @author Joachim Wuttke, Scientific Computing Group at MLZ (see CITATION) // // ************************************************************************************************ #ifndef FF_EULEROPERATIONS_H #define FF_EULEROPERATIONS_H #include "ff/Polyhedron.h" #include namespace ff::atomic { //! Returns a new Polyhedron with the faces containing the edge (iV0, iV1) removed. Polyhedron* kef(const Polyhedron& polyhedron, int iV0, int iV1); //! Kills one edge one vertex, where iV0 and iV1 are the indices of .vertices() Polyhedron* kev(const Polyhedron& polyhedron, int iV0, int iV1); //! Makes one vertex one edge, where iV0 and iV1 are the indices of .vertices() Polyhedron* mev(const Polyhedron& polyhedron, int iV0, int iV1, const R3& v); //! Makes one edge one face, where iF ist the face index and iE0,iE1 are the vertex indices Polyhedron* mef(const Polyhedron& polyhedron, int iF, int iE0, int iE1); //! Clips a Polyhedron by the z_cut offset xy-plane Polyhedron* z_clip(const Polyhedron& polyhedron, double z_cut,bool keepBelow = false); } // namespace ff::atomic #endif // FF_EULEROPERATIONS_H libformfactor-v0.3.2-7e235d29b1ab60f2e4788e7c819d7c0277aa2954/ff/Face.cpp000066400000000000000000000344771477075301700240350ustar00rootroot00000000000000// ************************************************************************************************ // // libformfactor: efficient and accurate computation of scattering form factors // //! @file ff/Face.cpp //! @brief Implements class Face //! //! @homepage https://jugit.fz-juelich.de/mlz/libformfactor //! @license GNU General Public License v3 or higher (see LICENSE) //! @copyright Forschungszentrum Jülich GmbH 2022 //! @author Joachim Wuttke, Scientific Computing Group at MLZ (see CITATION) // // ************************************************************************************************ #include "ff/Face.h" #include "ff/Edge.h" #include "ff/Factorial.h" #include #include #include #define _USE_MATH_DEFINES #include namespace { const double eps = 2e-16; const double qpa_limit_series = 1e-2; const int n_limit_series = 20; constexpr auto ReciprocalFactorialArray = ff_aux::generateReciprocalFactorialArray<171>(); complex_t sinc(const complex_t z) // cardinal sine function, sin(x)/x { // This is an exception from the rule that we must not test floating-point numbers for equality. // For small non-zero arguments, sin(z) returns quite accurately z or z-z^3/6. // There is no loss of precision in computing sin(z)/z. // Therefore there is no need for an expensive test like abs(z) precompute_edges_raw(const std::vector& V, double radius_2d) { size_t NV = V.size(); if (!NV) throw std::runtime_error("Invalid polyhedral face: no edges given"); if (NV < 3) throw std::runtime_error("Invalid polyhedral face: less than three edges"); // Initialize list of 'edges'. // Do not create an edge if two vertices are too close to each other. // TODO This is implemented in a somewhat sloppy way: we just skip an edge if it would // be too short. This leaves tiny open edges. In a clean implementation, we // rather should merge adjacent vertices before generating edges. std::vector result; for (size_t j = 0; j < NV; ++j) { size_t jj = (j + 1) % NV; if ((V[j] - V[jj]).mag() < 1e-14 * radius_2d) continue; // distance too short -> skip this edge result.emplace_back(V[j], V[jj]); } return result; } std::vector precompute_edges_with_symmetry(const std::vector& V, bool sym_S2, const R3& face_center, double radius_2d) { // only now deal with inversion symmetry std::vector result = precompute_edges_raw(V, radius_2d); size_t NE = result.size(); if (sym_S2) { if (NE & 1) throw std::runtime_error("Invalid polyhedral face: odd #edges violates symmetry S2"); NE /= 2; for (size_t j = 0; j < NE; ++j) { if ((result[j].R() + result[j + NE].R() - 2 * face_center).mag2() > pow(2e-12 * radius_2d, 2)) throw std::runtime_error( "Invalid polyhedral face: edge centers violate symmetry S2"); if ((result[j].E() + result[j + NE].E()).mag() > 1e-12 * radius_2d) throw std::runtime_error( "Invalid polyhedral face: edge vectors violate symmetry S2"); } // keep only half of the egdes result.erase(result.begin() + NE, result.end()); } return result; } R3 precompute_normal(const std::vector& edges) { size_t NE = edges.size(); if (NE < 3) throw std::invalid_argument("Face has less than three non-vanishing edges"); R3 result; for (size_t j = 0; j < NE; ++j) result += edges[j].E().cross(edges[(j + 1) % NE].E()); return result.unit_or_null(); } //! Returns distance of figure plane from origin double precompute_rperp(const std::vector& V, const R3& normal, double radius_2d) { const size_t NV = V.size(); const double estimate = V[0].dot(normal); double sum1 = 0; for (size_t j = 1; j < NV; ++j) { const double x = V[j].dot(normal); sum1 += (x - estimate); } const double rperp = estimate + sum1 / NV; return rperp; } double precompute_area(const std::vector& V, const R3& normal) { double result = 0; size_t NV = V.size(); // compute m_area for (size_t j = 0; j < NV; ++j) { size_t jj = (j + 1) % NV; result += normal.dot(V[j].cross(V[jj])) / 2; } return result; } //! Returns radius of circle that contains all vertices. double precompute_radius(const std::vector& V) { double diameter = 0; for (size_t j = 0; j < V.size(); ++j) for (size_t jj = j + 1; jj < V.size(); ++jj) diameter = std::max(diameter, (V[j] - V[jj]).mag()); return diameter / 2; } } // namespace #ifdef ALGORITHM_DIAGNOSTIC void ff::PolyhedralDiagnosis::reset() { order = 0; algo = 0; msg.clear(); }; std::string ff::PolyhedralDiagnosis::message() const { std::string result = "algo=" + std::to_string(algo) + ", order=" + std::to_string(order); if (!msg.empty()) result += ", msg:\n" + msg; return result; } bool ff::PolyhedralDiagnosis::operator==(const PolyhedralDiagnosis& other) const { return order == other.order && algo == other.algo; } bool ff::PolyhedralDiagnosis::operator!=(const PolyhedralDiagnosis& other) const { return !(*this == other); } #endif // ************************************************************************************************ // PolyhedralFace implementation // ************************************************************************************************ //! Sets internal variables for given vertex chain. //! @param V oriented vertex list //! @param _sym_S2 true if face has a perpedicular two-fold symmetry axis ff::Face::Face(const std::vector& V, bool sym_S2) : m_sym_S2(sym_S2) , m_radius_2d(precompute_radius(V)) , m_normal(precompute_normal(precompute_edges_raw(V, m_radius_2d))) , m_rperp(precompute_rperp(V, m_normal, m_radius_2d)) , m_area(precompute_area(V, m_normal)) , m_edges(precompute_edges_with_symmetry(V, m_sym_S2, m_rperp * m_normal, m_radius_2d)) { } ff::Face::~Face() = default; const std::vector& ff::Face::edges() const { return m_edges; } R3 ff::Face::center_of_polygon() const { if (m_sym_S2) return m_normal * m_rperp; const R3 scaled_normal = (2 / (3. * m_area)) * m_normal; R3 meanR; for (const Edge& e : m_edges) meanR += e.R(); meanR /= m_edges.size(); R3 result = meanR; for (const Edge& e : m_edges) result += (e.R() - meanR).cross(e.E()).dot(scaled_normal) * (e.R() - meanR); return result; } //! Sets qperp and qpa according to argument q and to this polygon's normal. void ff::Face::decompose_q(C3 q, complex_t& qperp, C3& qpa) const { qperp = m_normal.dot(q); qpa = q - qperp * m_normal; // improve numeric accuracy: qpa -= m_normal.dot(qpa) * m_normal; if (qpa.mag() < eps * std::abs(qperp)) qpa = C3(0., 0., 0.); } //! Returns core contribution to f_n complex_t ff::Face::ff_n_core(int m, C3 qpa, complex_t qperp) const { const C3 prevec = 2. * m_normal.cross(qpa); // complex conjugation not here but in .dot complex_t result = 0; const complex_t qrperp = qperp * m_rperp; for (size_t i = 0; i < m_edges.size(); ++i) { const Edge& e = m_edges[i]; const complex_t vfac = prevec.dot(e.E()); const complex_t tmp = e.contrib(m + 1, qpa, qrperp); result += vfac * tmp; // std::cout << std::scientific << std::showpos << std::setprecision(16) // << "DBX ff_n_core " << m << " " << vfac << " " << tmp // << " term=" << vfac * tmp << " sum=" << result << "\n"; } return result; } //! Returns contribution qn*f_n [of order q^(n+1)] from this face to the polyhedral form factor. complex_t ff::Face::ff_n(int n, C3 q) const { complex_t qn = q.dot(m_normal); // conj(q)*normal (dot is antilinear in 'this' argument) if (std::abs(qn) < eps * q.mag()) return 0.; complex_t qperp; C3 qpa; decompose_q(q, qperp, qpa); double qpa_mag2 = qpa.mag2(); if (qpa_mag2 == 0.) return qn * pow(qperp * m_rperp, n) * m_area * ReciprocalFactorialArray[n]; if (m_sym_S2) return qn * (ff_n_core(n, qpa, qperp) + ff_n_core(n, -qpa, qperp)) / qpa_mag2; complex_t tmp = ff_n_core(n, qpa, qperp); // std::cout << "DBX ff_n " << n << " " << qn << " " << tmp << " " << qpa_mag2 << "\n"; return qn * tmp / qpa_mag2; } //! Returns sum of n>=1 terms of qpa expansion of 2d form factor complex_t ff::Face::expansion(complex_t fac_even, complex_t fac_odd, C3 qpa, double abslevel) const { #ifdef ALGORITHM_DIAGNOSTIC polyhedralDiagnosis.algo += 1; #endif complex_t sum = 0; complex_t n_fac = I; int count_return_condition = 0; for (int n = 1; n < n_limit_series; ++n) { #ifdef ALGORITHM_DIAGNOSTIC polyhedralDiagnosis.order = std::max(polyhedralDiagnosis.order, n); #endif complex_t term = n_fac * (n & 1 ? fac_odd : fac_even) * ff_n_core(n, qpa, 0) / qpa.mag2(); // std::cout << std::setprecision(16) << " sum=" << sum << " +term=" << term << "\n"; sum += term; if (std::abs(term) <= eps * std::abs(sum) || std::abs(sum) < eps * abslevel) ++count_return_condition; else count_return_condition = 0; if (count_return_condition > 2) return sum; // regular exit n_fac = mul_I(n_fac); } throw std::runtime_error("Numeric error in polyhedral face: series f(q_pa) not converged"); } //! Returns core contribution to analytic 2d form factor. complex_t ff::Face::edge_sum_ff(C3 q, C3 qpa, bool sym_Ci) const { C3 prevec = m_normal.cross(qpa); // complex conjugation will take place in .dot complex_t sum = 0; complex_t vfacsum = 0; for (size_t i = 0; i < m_edges.size(); ++i) { const ff::Edge& e = m_edges[i]; complex_t qE = e.qE(qpa); complex_t qR = e.qR(qpa); complex_t Rfac = m_sym_S2 ? sin(qR) : (sym_Ci ? cos(e.qR(q)) : exp_I(qR)); complex_t vfac; if (m_sym_S2 || i < m_edges.size() - 1) { vfac = prevec.dot(e.E()); vfacsum += vfac; } else { vfac = -vfacsum; // to improve numeric accuracy: qcE_J = - sum_{j=0}^{J-1} qcE_j } complex_t term = vfac * sinc(qE) * Rfac; sum += term; // std::cout << std::scientific << std::showpos << std::setprecision(16) // << " sum=" << sum << " term=" << term << " vf=" << vfac << " qE=" << qE // << " qR=" << qR << " sinc=" << sinc(qE) << " Rfac=" << Rfac << "\n"; } return sum; } //! Returns the contribution ff(q) of this face to the polyhedral form factor. complex_t ff::Face::ff(C3 q, bool sym_Ci) const { complex_t qperp; C3 qpa; decompose_q(q, qperp, qpa); double qpa_red = m_radius_2d * qpa.mag(); complex_t qr_perp = qperp * m_rperp; complex_t ff0 = (sym_Ci ? 2. * I * sin(qr_perp) : exp_I(qr_perp)) * m_area; if (qpa_red == 0) return ff0; if (qpa_red < qpa_limit_series && !m_sym_S2) { // summation of power series complex_t fac_even; complex_t fac_odd; if (sym_Ci) { fac_even = 2. * mul_I(sin(qr_perp)); fac_odd = 2. * cos(qr_perp); } else { fac_even = exp_I(qr_perp); fac_odd = fac_even; } return ff0 + expansion(fac_even, fac_odd, qpa, std::abs(ff0)); } // direct evaluation of analytic formula complex_t prefac; if (m_sym_S2) prefac = sym_Ci ? -8. * sin(qr_perp) : 4. * mul_I(exp_I(qr_perp)); else prefac = sym_Ci ? 4. : 2. * exp_I(qr_perp); // std::cout << " qrperp=" << qr_perp << " => prefac=" << prefac << "\n"; return prefac * edge_sum_ff(q, qpa, sym_Ci) / mul_I(qpa.mag2()); } //! Two-dimensional form factor, for use in prism, from power series. complex_t ff::Face::ff_2D_expanded(C3 qpa) const { return m_area + expansion(1., 1., qpa, std::abs(m_area)); } //! Two-dimensional form factor, for use in prism, from sum over edge form factors. complex_t ff::Face::ff_2D_direct(C3 qpa) const { return (m_sym_S2 ? 4. : 2. / I) * edge_sum_ff(qpa, qpa, false) / qpa.mag2(); } //! Returns the two-dimensional form factor of this face, for use in a prism. complex_t ff::Face::ff_2D(C3 qpa) const { if (std::abs(qpa.dot(m_normal)) > eps * qpa.mag()) throw std::runtime_error( "Numeric error in polyhedral formfactor: ff_2D called with perpendicular q component"); double qpa_red = m_radius_2d * qpa.mag(); if (qpa_red == 0) return m_area; if (qpa_red < qpa_limit_series && !m_sym_S2) return ff_2D_expanded(qpa); return ff_2D_direct(qpa); } //! Throws if deviation from inversion symmetry is detected. Does not check vertices. void ff::Face::assert_Ci(const Face& other) const { if (std::abs(m_rperp - other.m_rperp) > std::abs(1e-15 * (m_rperp + other.m_rperp))) throw std::runtime_error( "Invalid polyhedron: faces with different distance from origin violate symmetry Ci"); if (std::abs(m_area - other.m_area) > 1e-15 * (m_area + other.m_area)) throw std::runtime_error( "Invalid polyhedron: faces with different areas violate symmetry Ci"); if ((m_normal + other.m_normal).mag() > 1e-14) throw std::runtime_error( "Invalid polyhedron: faces do not have opposite orientation, violating symmetry Ci"); } //! Returns true if point v, is located inside the infinite extruded volume defined by this polygon. bool ff::Face::is_inside(const R3& v) const { int n_rightOf = 0; // number of occasions v is located right of the edge for (const Edge& edge : m_edges) { R3 p = v - edge.R(); double o = (m_normal.cross(edge.E())).dot(p); if (o >= 0) n_rightOf++; else if (o < 0) n_rightOf--; if (m_sym_S2) { p = v + edge.R(); double o = (m_normal.cross(-edge.E())).dot(p); if (o >= 0) n_rightOf++; else if (o < 0) n_rightOf--; } } int edge_size = m_sym_S2 ? m_edges.size() * 2 : m_edges.size(); return abs(n_rightOf) == edge_size; } libformfactor-v0.3.2-7e235d29b1ab60f2e4788e7c819d7c0277aa2954/ff/Face.h000066400000000000000000000055331477075301700234710ustar00rootroot00000000000000// ************************************************************************************************ // // libformfactor: efficient and accurate computation of scattering form factors // //! @file ff/Face.h //! @brief Defines class Face //! //! @homepage https://jugit.fz-juelich.de/mlz/libformfactor //! @license GNU General Public License v3 or higher (see LICENSE) //! @copyright Forschungszentrum Jülich GmbH 2022 //! @author Joachim Wuttke, Scientific Computing Group at MLZ (see CITATION) // // ************************************************************************************************ #ifndef FF_FACE_H #define FF_FACE_H #include #include #include namespace ff { class Edge; #ifdef ALGORITHM_DIAGNOSTIC #include struct PolyhedralDiagnosis { int algo; int order; std::string msg; void reset(); std::string message() const; bool operator==(const PolyhedralDiagnosis&) const; bool operator!=(const PolyhedralDiagnosis&) const; }; inline PolyhedralDiagnosis polyhedralDiagnosis; #endif //! A polygon, for form factor computation. class Face { public: Face(const std::vector& _V, bool _sym_S2 = false); ~Face(); Face(const Face&) = delete; Face(Face&&) = default; double area() const { return m_area; } double radius() const { return m_radius_2d; } double pyramidalVolume() const { return m_rperp * m_area / 3; } //! Returns center of mass of the plane figure R3 center_of_polygon() const; //! Returns conj(q)*normal [BasicVector3D::dot is antilinear in 'this' argument] complex_t normalProjectionConj(C3 q) const { return q.dot(m_normal); } complex_t ff_n(int n, C3 q) const; complex_t ff(C3 q, bool sym_Ci) const; complex_t ff_2D(C3 qpa) const; complex_t ff_2D_direct(C3 qpa) const; // for TestTriangle complex_t ff_2D_expanded(C3 qpa) const; // for TestTriangle void assert_Ci(const Face& other) const; const std::vector& edges() const; const R3& normal() const { return m_normal; } bool is_symmetric() const { return m_sym_S2; } bool is_inside(const R3& v) const; private: const bool m_sym_S2; //!< if true, then edges obtainable by inversion are not provided const double m_radius_2d; //!< radius of enclosing cylinder const R3 m_normal; //!< normal vector of this polygon's plane const double m_rperp; //!< distance of polygon's plane from the origin, along 'm_normal' const double m_area; const std::vector m_edges; void decompose_q(C3 q, complex_t& qperp, C3& qpa) const; complex_t ff_n_core(int m, C3 qpa, complex_t qperp) const; complex_t edge_sum_ff(C3 q, C3 qpa, bool sym_Ci) const; complex_t expansion(complex_t fac_even, complex_t fac_odd, C3 qpa, double abslevel) const; }; } // namespace ff #endif // FF_FACE_H libformfactor-v0.3.2-7e235d29b1ab60f2e4788e7c819d7c0277aa2954/ff/Factorial.h000066400000000000000000000027231477075301700245350ustar00rootroot00000000000000// ************************************************************************************************ // // libformfactor: efficient and accurate computation of scattering form factors // //! @file ff/Factorial.h //! @brief Precomputes 1/n! //! //! @homepage https://jugit.fz-juelich.de/mlz/libformfactor //! @license GNU General Public License v3 or higher (see LICENSE) //! @copyright Forschungszentrum Jülich GmbH 2022 //! @author Joachim Wuttke, Scientific Computing Group at MLZ (see CITATION) // // ************************************************************************************************ #ifndef FF_FACTORIAL_H #define FF_FACTORIAL_H #include #include #include namespace { template struct ReciprocalFactorial { static constexpr double value = ReciprocalFactorial::value / N; }; template <> struct ReciprocalFactorial<0> { static constexpr double value = 1.0; }; template