text:
import JSON

const UNICODE_BASIC_LATIN = 1
const UNICODE_LATIN_1_SUPPLEMENT = 2
const UNICODE_LATIN_EXTENDED_A = 3
const UNICODE_LATIN_EXTENDED_B = 4
const UNICODE_IPA_EXTENSIONS = 5
const UNICODE_SPACING_MODIFIER_LETTERS = 6
const UNICODE_COMBINING_DIACRITICAL_MARKS = 7
const UNICODE_GREEK_AND_COPTIC = 8
const UNICODE_CYRILLIC = 9
const UNICODE_CYRILLIC_SUPPLEMENT = 10
const UNICODE_ARMENIAN = 11
const UNICODE_HEBREW = 12
const UNICODE_ARABIC = 13
const UNICODE_SYRIAC = 14
const UNICODE_ARABIC_SUPPLEMENT = 15
const UNICODE_THAANA = 16
const UNICODE_NKO = 17
const UNICODE_SAMARITAN = 18
const UNICODE_MANDAIC = 19
const UNICODE_ARABIC_EXTENDED_A = 20
const UNICODE_DEVANAGARI = 21
const UNICODE_BENGALI = 22
const UNICODE_GURMUKHI = 23
const UNICODE_GUJARATI = 24
const UNICODE_ORIYA = 25
const UNICODE_TAMIL = 26
const UNICODE_TELUGU = 27
const UNICODE_KANNADA = 28
const UNICODE_MALAYALAM = 29
const UNICODE_SINHALA = 30
const UNICODE_THAI = 31
const UNICODE_LAO = 32
const UNICODE_TIBETAN = 33
const UNICODE_MYANMAR = 34
const UNICODE_GEORGIAN = 35
const UNICODE_HANGUL_JAMO = 36
const UNICODE_ETHIOPIC = 37
const UNICODE_ETHIOPIC_SUPPLEMENT = 38
const UNICODE_CHEROKEE = 39
const UNICODE_UNIFIED_CANADIAN_ABORIGINAL_SYLLABICS = 40
const UNICODE_OGHAM = 41
const UNICODE_RUNIC = 42
const UNICODE_TAGALOG = 43
const UNICODE_HANUNOO = 44
const UNICODE_BUHID = 45
const UNICODE_TAGBANWA = 46
const UNICODE_KHMER = 47
const UNICODE_MONGOLIAN = 48
const UNICODE_UNIFIED_CANADIAN_ABORIGINAL_SYLLABICS_EXTENDED = 49
const UNICODE_LIMBU = 50
const UNICODE_TAI_LE = 51
const UNICODE_NEW_TAI_LUE = 52
const UNICODE_KHMER_SYMBOLS = 53
const UNICODE_BUGINESE = 54
const UNICODE_TAI_THAM = 55
const UNICODE_BALINESE = 56
const UNICODE_SUNDANESE = 57
const UNICODE_BATAK = 58
const UNICODE_LEPCHA = 59
const UNICODE_OL_CHIKI = 60
const UNICODE_SUNDANESE_SUPPLEMENT = 61
const UNICODE_VEDIC_EXTENSIONS = 62
const UNICODE_PHONETIC_EXTENSIONS = 63
const UNICODE_PHONETIC_EXTENSIONS_SUPPLEMENT = 64
const UNICODE_COMBINING_DIACRITICAL_MARKS_SUPPLEMENT = 65
const UNICODE_LATIN_EXTENDED_ADDITIONAL = 66
const UNICODE_GREEK_EXTENDED = 67
const UNICODE_GENERAL_PUNCTUATION = 68
const UNICODE_SUPERSCRIPTS_AND_SUBSCRIPTS = 69
const UNICODE_CURRENCY_SYMBOLS = 70
const UNICODE_COMBINING_DIACRITICAL_MARKS_FOR_SYMBOLS = 71
const UNICODE_LETTERLIKE_SYMBOLS = 72
const UNICODE_NUMBER_FORMS = 73
const UNICODE_ARROWS = 74
const UNICODE_MATHEMATICAL_OPERATORS = 75
const UNICODE_MISCELLANEOUS_TECHNICAL = 76
const UNICODE_CONTROL_PICTURES = 77
const UNICODE_OPTICAL_CHARACTER_RECOGNITION = 78
const UNICODE_ENCLOSED_ALPHANUMERICS = 79
const UNICODE_BOX_DRAWING = 80
const UNICODE_BLOCK_ELEMENTS = 81
const UNICODE_GEOMETRIC_SHAPES = 82
const UNICODE_MISCELLANEOUS_SYMBOLS = 83
const UNICODE_DINGBATS = 84
const UNICODE_MISCELLANEOUS_MATHEMATICAL_SYMBOLS_A = 85
const UNICODE_SUPPLEMENTAL_ARROWS_A = 86
const UNICODE_BRAILLE_PATTERNS = 87
const UNICODE_SUPPLEMENTAL_ARROWS_B = 88
const UNICODE_MISCELLANEOUS_MATHEMATICAL_SYMBOLS_B = 89
const UNICODE_SUPPLEMENTAL_MATHEMATICAL_OPERATORS = 90
const UNICODE_MISCELLANEOUS_SYMBOLS_AND_ARROWS = 91
const UNICODE_GLAGOLITIC = 92
const UNICODE_LATIN_EXTENDED_C = 93
const UNICODE_COPTIC = 94
const UNICODE_GEORGIAN_SUPPLEMENT = 95
const UNICODE_TIFINAGH = 96
const UNICODE_ETHIOPIC_EXTENDED = 97
const UNICODE_CYRILLIC_EXTENDED_A = 98
const UNICODE_SUPPLEMENTAL_PUNCTUATION = 99
const UNICODE_CJK_RADICALS_SUPPLEMENT = 100
const UNICODE_KANGXI_RADICALS = 101
const UNICODE_IDEOGRAPHIC_DESCRIPTION_CHARACTERS = 102
const UNICODE_CJK_SYMBOLS_AND_PUNCTUATION = 103
const UNICODE_HIRAGANA = 104
const UNICODE_KATAKANA = 105
const UNICODE_BOPOMOFO = 106
const UNICODE_HANGUL_COMPATIBILITY_JAMO = 107
const UNICODE_KANBUN = 108
const UNICODE_BOPOMOFO_EXTENDED = 109
const UNICODE_CJK_STROKES = 110
const UNICODE_KATAKANA_PHONETIC_EXTENSIONS = 111
const UNICODE_ENCLOSED_CJK_LETTERS_AND_MONTHS = 112
const UNICODE_CJK_COMPATIBILITY = 113
const UNICODE_CJK_UNIFIED_IDEOGRAPHS_EXTENSION_A = 114
const UNICODE_YIJING_HEXAGRAM_SYMBOLS = 115
const UNICODE_CJK_UNIFIED_IDEOGRAPHS = 116
const UNICODE_YI_SYLLABLES = 117
const UNICODE_YI_RADICALS = 118
const UNICODE_LISU = 119
const UNICODE_VAI = 120
const UNICODE_CYRILLIC_EXTENDED_B = 121
const UNICODE_BAMUM = 122
const UNICODE_MODIFIER_TONE_LETTERS = 123
const UNICODE_LATIN_EXTENDED_D = 124
const UNICODE_SYLOTI_NAGRI = 125
const UNICODE_COMMON_INDIC_NUMBER_FORMS = 126
const UNICODE_PHAGS_PA = 127
const UNICODE_SAURASHTRA = 128
const UNICODE_DEVANAGARI_EXTENDED = 129
const UNICODE_KAYAH_LI = 130
const UNICODE_REJANG = 131
const UNICODE_HANGUL_JAMO_EXTENDED_A = 132
const UNICODE_JAVANESE = 133
const UNICODE_CHAM = 134
const UNICODE_MYANMAR_EXTENDED_A = 135
const UNICODE_TAI_VIET = 136
const UNICODE_MEETEI_MAYEK_EXTENSIONS = 137
const UNICODE_ETHIOPIC_EXTENDED_A = 138
const UNICODE_MEETEI_MAYEK = 139
const UNICODE_HANGUL_SYLLABLES = 140
const UNICODE_HANGUL_JAMO_EXTENDED_B = 141
const UNICODE_HIGH_SURROGATES = 142
const UNICODE_HIGH_PRIVATE_USE_SURROGATES = 143
const UNICODE_LOW_SURROGATES = 144
const UNICODE_PRIVATE_USE_AREA = 145
const UNICODE_CJK_COMPATIBILITY_IDEOGRAPHS = 146
const UNICODE_ALPHABETIC_PRESENTATION_FORMS = 147
const UNICODE_ARABIC_PRESENTATION_FORMS_A = 148
const UNICODE_VARIATION_SELECTORS = 149
const UNICODE_VERTICAL_FORMS = 150
const UNICODE_COMBINING_HALF_MARKS = 151
const UNICODE_CJK_COMPATIBILITY_FORMS = 152
const UNICODE_SMALL_FORM_VARIANTS = 153
const UNICODE_ARABIC_PRESENTATION_FORMS_B = 154
const UNICODE_HALFWIDTH_AND_FULLWIDTH_FORMS = 155
const UNICODE_SPECIALS = 156
const UNICODE_LINEAR_B_SYLLABARY = 157
const UNICODE_LINEAR_B_IDEOGRAMS = 158
const UNICODE_AEGEAN_NUMBERS = 159
const UNICODE_ANCIENT_GREEK_NUMBERS = 160
const UNICODE_ANCIENT_SYMBOLS = 161
const UNICODE_PHAISTOS_DISC = 162
const UNICODE_LYCIAN = 163
const UNICODE_CARIAN = 164
const UNICODE_OLD_ITALIC = 165
const UNICODE_GOTHIC = 166
const UNICODE_UGARITIC = 167
const UNICODE_OLD_PERSIAN = 168
const UNICODE_DESERET = 169
const UNICODE_SHAVIAN = 170
const UNICODE_OSMANYA = 171
const UNICODE_CYPRIOT_SYLLABARY = 172
const UNICODE_IMPERIAL_ARAMAIC = 173
const UNICODE_PHOENICIAN = 174
const UNICODE_LYDIAN = 175
const UNICODE_MEROITIC_HIEROGLYPHS = 176
const UNICODE_MEROITIC_CURSIVE = 177
const UNICODE_KHAROSHTHI = 178
const UNICODE_OLD_SOUTH_ARABIAN = 179
const UNICODE_AVESTAN = 180
const UNICODE_INSCRIPTIONAL_PARTHIAN = 181
const UNICODE_INSCRIPTIONAL_PAHLAVI = 182
const UNICODE_OLD_TURKIC = 183
const UNICODE_RUMI_NUMERAL_SYMBOLS = 184
const UNICODE_BRAHMI = 185
const UNICODE_KAITHI = 186
const UNICODE_SORA_SOMPENG = 187
const UNICODE_CHAKMA = 188
const UNICODE_SHARADA = 189
const UNICODE_TAKRI = 190
const UNICODE_CUNEIFORM = 191
const UNICODE_CUNEIFORM_NUMBERS_AND_PUNCTUATION = 192
const UNICODE_EGYPTIAN_HIEROGLYPHS = 193
const UNICODE_BAMUM_SUPPLEMENT = 194
const UNICODE_MIAO = 195
const UNICODE_KANA_SUPPLEMENT = 196
const UNICODE_BYZANTINE_MUSICAL_SYMBOLS = 197
const UNICODE_MUSICAL_SYMBOLS = 198
const UNICODE_ANCIENT_GREEK_MUSICAL_NOTATION = 199
const UNICODE_TAI_XUAN_JING_SYMBOLS = 200
const UNICODE_COUNTING_ROD_NUMERALS = 201
const UNICODE_MATHEMATICAL_ALPHANUMERIC_SYMBOLS = 202
const UNICODE_ARABIC_MATHEMATICAL_ALPHABETIC_SYMBOLS = 203
const UNICODE_MAHJONG_TILES = 204
const UNICODE_DOMINO_TILES = 205
const UNICODE_PLAYING_CARDS = 206
const UNICODE_ENCLOSED_ALPHANUMERIC_SUPPLEMENT = 207
const UNICODE_ENCLOSED_IDEOGRAPHIC_SUPPLEMENT = 208
const UNICODE_MISCELLANEOUS_SYMBOLS_AND_PICTOGRAPHS = 209
const UNICODE_EMOTICONS = 210
const UNICODE_TRANSPORT_AND_MAP_SYMBOLS = 211
const UNICODE_ALCHEMICAL_SYMBOLS = 212
const UNICODE_CJK_UNIFIED_IDEOGRAPHS_EXTENSION_B = 213
const UNICODE_CJK_UNIFIED_IDEOGRAPHS_EXTENSION_C = 214
const UNICODE_CJK_UNIFIED_IDEOGRAPHS_EXTENSION_D = 215
const UNICODE_CJK_COMPATIBILITY_IDEOGRAPHS_SUPPLEMENT = 216
const UNICODE_TAGS = 217
const UNICODE_VARIATION_SELECTORS_SUPPLEMENT = 218
const UNICODE_SUPPLEMENTARY_PRIVATE_USE_AREA_A = 219
const UNICODE_SUPPLEMENTARY_PRIVATE_USE_AREA_B = 220

const _unicode_blocks = [
    (UNICODE_BASIC_LATIN, 0x0000, 0x007F),
    (UNICODE_LATIN_1_SUPPLEMENT, 0x0080, 0x00FF),
    (UNICODE_LATIN_EXTENDED_A, 0x0100, 0x017F),
    (UNICODE_LATIN_EXTENDED_B, 0x0180, 0x024F),
    (UNICODE_IPA_EXTENSIONS, 0x0250, 0x02AF),
    (UNICODE_SPACING_MODIFIER_LETTERS, 0x02B0, 0x02FF),
    (UNICODE_COMBINING_DIACRITICAL_MARKS, 0x0300, 0x036F),
    (UNICODE_GREEK_AND_COPTIC, 0x0370, 0x03FF),
    (UNICODE_CYRILLIC, 0x0400, 0x04FF),
    (UNICODE_CYRILLIC_SUPPLEMENT, 0x0500, 0x052F),
    (UNICODE_ARMENIAN, 0x0530, 0x058F),
    (UNICODE_HEBREW, 0x0590, 0x05FF),
    (UNICODE_ARABIC, 0x0600, 0x06FF),
    (UNICODE_SYRIAC, 0x0700, 0x074F),
    (UNICODE_ARABIC_SUPPLEMENT, 0x0750, 0x077F),
    (UNICODE_THAANA, 0x0780, 0x07BF),
    (UNICODE_NKO, 0x07C0, 0x07FF),
    (UNICODE_SAMARITAN, 0x0800, 0x083F),
    (UNICODE_MANDAIC, 0x0840, 0x085F),
    (UNICODE_ARABIC_EXTENDED_A, 0x08A0, 0x08FF),
    (UNICODE_DEVANAGARI, 0x0900, 0x097F),
    (UNICODE_BENGALI, 0x0980, 0x09FF),
    (UNICODE_GURMUKHI, 0x0A00, 0x0A7F),
    (UNICODE_GUJARATI, 0x0A80, 0x0AFF),
    (UNICODE_ORIYA, 0x0B00, 0x0B7F),
    (UNICODE_TAMIL, 0x0B80, 0x0BFF),
    (UNICODE_TELUGU, 0x0C00, 0x0C7F),
    (UNICODE_KANNADA, 0x0C80, 0x0CFF),
    (UNICODE_MALAYALAM, 0x0D00, 0x0D7F),
    (UNICODE_SINHALA, 0x0D80, 0x0DFF),
    (UNICODE_THAI, 0x0E00, 0x0E7F),
    (UNICODE_LAO, 0x0E80, 0x0EFF),
    (UNICODE_TIBETAN, 0x0F00, 0x0FFF),
    (UNICODE_MYANMAR, 0x1000, 0x109F),
    (UNICODE_GEORGIAN, 0x10A0, 0x10FF),
    (UNICODE_HANGUL_JAMO, 0x1100, 0x11FF),
    (UNICODE_ETHIOPIC, 0x1200, 0x137F),
    (UNICODE_ETHIOPIC_SUPPLEMENT, 0x1380, 0x139F),
    (UNICODE_CHEROKEE, 0x13A0, 0x13FF),
    (UNICODE_UNIFIED_CANADIAN_ABORIGINAL_SYLLABICS, 0x1400, 0x167F),
    (UNICODE_OGHAM, 0x1680, 0x169F),
    (UNICODE_RUNIC, 0x16A0, 0x16FF),
    (UNICODE_TAGALOG, 0x1700, 0x171F),
    (UNICODE_HANUNOO, 0x1720, 0x173F),
    (UNICODE_BUHID, 0x1740, 0x175F),
    (UNICODE_TAGBANWA, 0x1760, 0x177F),
    (UNICODE_KHMER, 0x1780, 0x17FF),
    (UNICODE_MONGOLIAN, 0x1800, 0x18AF),
    (UNICODE_UNIFIED_CANADIAN_ABORIGINAL_SYLLABICS_EXTENDED, 0x18B0, 0x18FF),
    (UNICODE_LIMBU, 0x1900, 0x194F),
    (UNICODE_TAI_LE, 0x1950, 0x197F),
    (UNICODE_NEW_TAI_LUE, 0x1980, 0x19DF),
    (UNICODE_KHMER_SYMBOLS, 0x19E0, 0x19FF),
    (UNICODE_BUGINESE, 0x1A00, 0x1A1F),
    (UNICODE_TAI_THAM, 0x1A20, 0x1AAF),
    (UNICODE_BALINESE, 0x1B00, 0x1B7F),
    (UNICODE_SUNDANESE, 0x1B80, 0x1BBF),
    (UNICODE_BATAK, 0x1BC0, 0x1BFF),
    (UNICODE_LEPCHA, 0x1C00, 0x1C4F),
    (UNICODE_OL_CHIKI, 0x1C50, 0x1C7F),
    (UNICODE_SUNDANESE_SUPPLEMENT, 0x1CC0, 0x1CCF),
    (UNICODE_VEDIC_EXTENSIONS, 0x1CD0, 0x1CFF),
    (UNICODE_PHONETIC_EXTENSIONS, 0x1D00, 0x1D7F),
    (UNICODE_PHONETIC_EXTENSIONS_SUPPLEMENT, 0x1D80, 0x1DBF),
    (UNICODE_COMBINING_DIACRITICAL_MARKS_SUPPLEMENT, 0x1DC0, 0x1DFF),
    (UNICODE_LATIN_EXTENDED_ADDITIONAL, 0x1E00, 0x1EFF),
    (UNICODE_GREEK_EXTENDED, 0x1F00, 0x1FFF),
    (UNICODE_GENERAL_PUNCTUATION, 0x2000, 0x206F),
    (UNICODE_SUPERSCRIPTS_AND_SUBSCRIPTS, 0x2070, 0x209F),
    (UNICODE_CURRENCY_SYMBOLS, 0x20A0, 0x20CF),
    (UNICODE_COMBINING_DIACRITICAL_MARKS_FOR_SYMBOLS, 0x20D0, 0x20FF),
    (UNICODE_LETTERLIKE_SYMBOLS, 0x2100, 0x214F),
    (UNICODE_NUMBER_FORMS, 0x2150, 0x218F),
    (UNICODE_ARROWS, 0x2190, 0x21FF),
    (UNICODE_MATHEMATICAL_OPERATORS, 0x2200, 0x22FF),
    (UNICODE_MISCELLANEOUS_TECHNICAL, 0x2300, 0x23FF),
    (UNICODE_CONTROL_PICTURES, 0x2400, 0x243F),
    (UNICODE_OPTICAL_CHARACTER_RECOGNITION, 0x2440, 0x245F),
    (UNICODE_ENCLOSED_ALPHANUMERICS, 0x2460, 0x24FF),
    (UNICODE_BOX_DRAWING, 0x2500, 0x257F),
    (UNICODE_BLOCK_ELEMENTS, 0x2580, 0x259F),
    (UNICODE_GEOMETRIC_SHAPES, 0x25A0, 0x25FF),
    (UNICODE_MISCELLANEOUS_SYMBOLS, 0x2600, 0x26FF),
    (UNICODE_DINGBATS, 0x2700, 0x27BF),
    (UNICODE_MISCELLANEOUS_MATHEMATICAL_SYMBOLS_A, 0x27C0, 0x27EF),
    (UNICODE_SUPPLEMENTAL_ARROWS_A, 0x27F0, 0x27FF),
    (UNICODE_BRAILLE_PATTERNS, 0x2800, 0x28FF),
    (UNICODE_SUPPLEMENTAL_ARROWS_B, 0x2900, 0x297F),
    (UNICODE_MISCELLANEOUS_MATHEMATICAL_SYMBOLS_B, 0x2980, 0x29FF),
    (UNICODE_SUPPLEMENTAL_MATHEMATICAL_OPERATORS, 0x2A00, 0x2AFF),
    (UNICODE_MISCELLANEOUS_SYMBOLS_AND_ARROWS, 0x2B00, 0x2BFF),
    (UNICODE_GLAGOLITIC, 0x2C00, 0x2C5F),
    (UNICODE_LATIN_EXTENDED_C, 0x2C60, 0x2C7F),
    (UNICODE_COPTIC, 0x2C80, 0x2CFF),
    (UNICODE_GEORGIAN_SUPPLEMENT, 0x2D00, 0x2D2F),
    (UNICODE_TIFINAGH, 0x2D30, 0x2D7F),
    (UNICODE_ETHIOPIC_EXTENDED, 0x2D80, 0x2DDF),
    (UNICODE_CYRILLIC_EXTENDED_A, 0x2DE0, 0x2DFF),
    (UNICODE_SUPPLEMENTAL_PUNCTUATION, 0x2E00, 0x2E7F),
    (UNICODE_CJK_RADICALS_SUPPLEMENT, 0x2E80, 0x2EFF),
    (UNICODE_KANGXI_RADICALS, 0x2F00, 0x2FDF),
    (UNICODE_IDEOGRAPHIC_DESCRIPTION_CHARACTERS, 0x2FF0, 0x2FFF),
    (UNICODE_CJK_SYMBOLS_AND_PUNCTUATION, 0x3000, 0x303F),
    (UNICODE_HIRAGANA, 0x3040, 0x309F),
    (UNICODE_KATAKANA, 0x30A0, 0x30FF),
    (UNICODE_BOPOMOFO, 0x3100, 0x312F),
    (UNICODE_HANGUL_COMPATIBILITY_JAMO, 0x3130, 0x318F),
    (UNICODE_KANBUN, 0x3190, 0x319F),
    (UNICODE_BOPOMOFO_EXTENDED, 0x31A0, 0x31BF),
    (UNICODE_CJK_STROKES, 0x31C0, 0x31EF),
    (UNICODE_KATAKANA_PHONETIC_EXTENSIONS, 0x31F0, 0x31FF),
    (UNICODE_ENCLOSED_CJK_LETTERS_AND_MONTHS, 0x3200, 0x32FF),
    (UNICODE_CJK_COMPATIBILITY, 0x3300, 0x33FF),
    (UNICODE_CJK_UNIFIED_IDEOGRAPHS_EXTENSION_A, 0x3400, 0x4DBF),
    (UNICODE_YIJING_HEXAGRAM_SYMBOLS, 0x4DC0, 0x4DFF),
    (UNICODE_CJK_UNIFIED_IDEOGRAPHS, 0x4E00, 0x9FFF),
    (UNICODE_YI_SYLLABLES, 0xA000, 0xA48F),
    (UNICODE_YI_RADICALS, 0xA490, 0xA4CF),
    (UNICODE_LISU, 0xA4D0, 0xA4FF),
    (UNICODE_VAI, 0xA500, 0xA63F),
    (UNICODE_CYRILLIC_EXTENDED_B, 0xA640, 0xA69F),
    (UNICODE_BAMUM, 0xA6A0, 0xA6FF),
    (UNICODE_MODIFIER_TONE_LETTERS, 0xA700, 0xA71F),
    (UNICODE_LATIN_EXTENDED_D, 0xA720, 0xA7FF),
    (UNICODE_SYLOTI_NAGRI, 0xA800, 0xA82F),
    (UNICODE_COMMON_INDIC_NUMBER_FORMS, 0xA830, 0xA83F),
    (UNICODE_PHAGS_PA, 0xA840, 0xA87F),
    (UNICODE_SAURASHTRA, 0xA880, 0xA8DF),
    (UNICODE_DEVANAGARI_EXTENDED, 0xA8E0, 0xA8FF),
    (UNICODE_KAYAH_LI, 0xA900, 0xA92F),
    (UNICODE_REJANG, 0xA930, 0xA95F),
    (UNICODE_HANGUL_JAMO_EXTENDED_A, 0xA960, 0xA97F),
    (UNICODE_JAVANESE, 0xA980, 0xA9DF),
    (UNICODE_CHAM, 0xAA00, 0xAA5F),
    (UNICODE_MYANMAR_EXTENDED_A, 0xAA60, 0xAA7F),
    (UNICODE_TAI_VIET, 0xAA80, 0xAADF),
    (UNICODE_MEETEI_MAYEK_EXTENSIONS, 0xAAE0, 0xAAFF),
    (UNICODE_ETHIOPIC_EXTENDED_A, 0xAB00, 0xAB2F),
    (UNICODE_MEETEI_MAYEK, 0xABC0, 0xABFF),
    (UNICODE_HANGUL_SYLLABLES, 0xAC00, 0xD7AF),
    (UNICODE_HANGUL_JAMO_EXTENDED_B, 0xD7B0, 0xD7FF),
    (UNICODE_HIGH_SURROGATES, 0xD800, 0xDB7F),
    (UNICODE_HIGH_PRIVATE_USE_SURROGATES, 0xDB80, 0xDBFF),
    (UNICODE_LOW_SURROGATES, 0xDC00, 0xDFFF),
    (UNICODE_PRIVATE_USE_AREA, 0xE000, 0xF8FF),
    (UNICODE_CJK_COMPATIBILITY_IDEOGRAPHS, 0xF900, 0xFAFF),
    (UNICODE_ALPHABETIC_PRESENTATION_FORMS, 0xFB00, 0xFB4F),
    (UNICODE_ARABIC_PRESENTATION_FORMS_A, 0xFB50, 0xFDFF),
    (UNICODE_VARIATION_SELECTORS, 0xFE00, 0xFE0F),
    (UNICODE_VERTICAL_FORMS, 0xFE10, 0xFE1F),
    (UNICODE_COMBINING_HALF_MARKS, 0xFE20, 0xFE2F),
    (UNICODE_CJK_COMPATIBILITY_FORMS, 0xFE30, 0xFE4F),
    (UNICODE_SMALL_FORM_VARIANTS, 0xFE50, 0xFE6F),
    (UNICODE_ARABIC_PRESENTATION_FORMS_B, 0xFE70, 0xFEFF),
    (UNICODE_HALFWIDTH_AND_FULLWIDTH_FORMS, 0xFF00, 0xFFEF),
    (UNICODE_SPECIALS, 0xFFF0, 0xFFFF),
    (UNICODE_LINEAR_B_SYLLABARY, 0x10000, 0x1007F),
    (UNICODE_LINEAR_B_IDEOGRAMS, 0x10080, 0x100FF),
    (UNICODE_AEGEAN_NUMBERS, 0x10100, 0x1013F),
    (UNICODE_ANCIENT_GREEK_NUMBERS, 0x10140, 0x1018F),
    (UNICODE_ANCIENT_SYMBOLS, 0x10190, 0x101CF),
    (UNICODE_PHAISTOS_DISC, 0x101D0, 0x101FF),
    (UNICODE_LYCIAN, 0x10280, 0x1029F),
    (UNICODE_CARIAN, 0x102A0, 0x102DF),
    (UNICODE_OLD_ITALIC, 0x10300, 0x1032F),
    (UNICODE_GOTHIC, 0x10330, 0x1034F),
    (UNICODE_UGARITIC, 0x10380, 0x1039F),
    (UNICODE_OLD_PERSIAN, 0x103A0, 0x103DF),
    (UNICODE_DESERET, 0x10400, 0x1044F),
    (UNICODE_SHAVIAN, 0x10450, 0x1047F),
    (UNICODE_OSMANYA, 0x10480, 0x104AF),
    (UNICODE_CYPRIOT_SYLLABARY, 0x10800, 0x1083F),
    (UNICODE_IMPERIAL_ARAMAIC, 0x10840, 0x1085F),
    (UNICODE_PHOENICIAN, 0x10900, 0x1091F),
    (UNICODE_LYDIAN, 0x10920, 0x1093F),
    (UNICODE_MEROITIC_HIEROGLYPHS, 0x10980, 0x1099F),
    (UNICODE_MEROITIC_CURSIVE, 0x109A0, 0x109FF),
    (UNICODE_KHAROSHTHI, 0x10A00, 0x10A5F),
    (UNICODE_OLD_SOUTH_ARABIAN, 0x10A60, 0x10A7F),
    (UNICODE_AVESTAN, 0x10B00, 0x10B3F),
    (UNICODE_INSCRIPTIONAL_PARTHIAN, 0x10B40, 0x10B5F),
    (UNICODE_INSCRIPTIONAL_PAHLAVI, 0x10B60, 0x10B7F),
    (UNICODE_OLD_TURKIC, 0x10C00, 0x10C4F),
    (UNICODE_RUMI_NUMERAL_SYMBOLS, 0x10E60, 0x10E7F),
    (UNICODE_BRAHMI, 0x11000, 0x1107F),
    (UNICODE_KAITHI, 0x11080, 0x110CF),
    (UNICODE_SORA_SOMPENG, 0x110D0, 0x110FF),
    (UNICODE_CHAKMA, 0x11100, 0x1114F),
    (UNICODE_SHARADA, 0x11180, 0x111DF),
    (UNICODE_TAKRI, 0x11680, 0x116CF),
    (UNICODE_CUNEIFORM, 0x12000, 0x123FF),
    (UNICODE_CUNEIFORM_NUMBERS_AND_PUNCTUATION, 0x12400, 0x1247F),
    (UNICODE_EGYPTIAN_HIEROGLYPHS, 0x13000, 0x1342F),
    (UNICODE_BAMUM_SUPPLEMENT, 0x16800, 0x16A3F),
    (UNICODE_MIAO, 0x16F00, 0x16F9F),
    (UNICODE_KANA_SUPPLEMENT, 0x1B000, 0x1B0FF),
    (UNICODE_BYZANTINE_MUSICAL_SYMBOLS, 0x1D000, 0x1D0FF),
    (UNICODE_MUSICAL_SYMBOLS, 0x1D100, 0x1D1FF),
    (UNICODE_ANCIENT_GREEK_MUSICAL_NOTATION, 0x1D200, 0x1D24F),
    (UNICODE_TAI_XUAN_JING_SYMBOLS, 0x1D300, 0x1D35F),
    (UNICODE_COUNTING_ROD_NUMERALS, 0x1D360, 0x1D37F),
    (UNICODE_MATHEMATICAL_ALPHANUMERIC_SYMBOLS, 0x1D400, 0x1D7FF),
    (UNICODE_ARABIC_MATHEMATICAL_ALPHABETIC_SYMBOLS, 0x1EE00, 0x1EEFF),
    (UNICODE_MAHJONG_TILES, 0x1F000, 0x1F02F),
    (UNICODE_DOMINO_TILES, 0x1F030, 0x1F09F),
    (UNICODE_PLAYING_CARDS, 0x1F0A0, 0x1F0FF),
    (UNICODE_ENCLOSED_ALPHANUMERIC_SUPPLEMENT, 0x1F100, 0x1F1FF),
    (UNICODE_ENCLOSED_IDEOGRAPHIC_SUPPLEMENT, 0x1F200, 0x1F2FF),
    (UNICODE_MISCELLANEOUS_SYMBOLS_AND_PICTOGRAPHS, 0x1F300, 0x1F5FF),
    (UNICODE_EMOTICONS, 0x1F600, 0x1F64F),
    (UNICODE_TRANSPORT_AND_MAP_SYMBOLS, 0x1F680, 0x1F6FF),
    (UNICODE_ALCHEMICAL_SYMBOLS, 0x1F700, 0x1F77F),
    (UNICODE_CJK_UNIFIED_IDEOGRAPHS_EXTENSION_B, 0x20000, 0x2A6DF),
    (UNICODE_CJK_UNIFIED_IDEOGRAPHS_EXTENSION_C, 0x2A700, 0x2B73F),
    (UNICODE_CJK_UNIFIED_IDEOGRAPHS_EXTENSION_D, 0x2B740, 0x2B81F),
    (UNICODE_CJK_COMPATIBILITY_IDEOGRAPHS_SUPPLEMENT, 0x2F800, 0x2FA1F),
    (UNICODE_TAGS, 0xE0000, 0xE007F),
    (UNICODE_VARIATION_SELECTORS_SUPPLEMENT, 0xE0100, 0xE01EF),
    (UNICODE_SUPPLEMENTARY_PRIVATE_USE_AREA_A, 0xF0000, 0xFFFFF),
    (UNICODE_SUPPLEMENTARY_PRIVATE_USE_AREA_B, 0x100000, 0x10FFFF),
]

# load CJK Mapping
const CJK_MAP = Ref{Dict{String, String}}(Dict{String, String}())
open(joinpath(dirname(pathof(LanguageDetect)), "CJK_MAP.json")) do reader
    CJK_MAP[] = JSON.parse(reader)
end

function unicode_block(ch::Char)::Int
    cp = Int(ch)
    # special case basic latin
    if cp <= Int(0x7F)
        return UNICODE_BASIC_LATIN
    end

    j = length(_unicode_blocks)
    i = 1
    # binary search
    while i <= j
        mid = (i + j) >> 1
        name, s, e = _unicode_blocks[mid]
        s = Int(s)
        e = Int(e)
        if s <= cp < e
            return name
        end
        if cp  < s
            j = mid - 1
        else
            i = mid + 1
        end
    end
end

function text_normalize(text::String)::String
    #= normalize text
    Args:
      text: String, input string
    =#
    # to array
    text = split(text, "")
    chars = Vector{Char}()
    for ch in text
        ch = first(ch)
        block = unicode_block(ch)
        if block == UNICODE_BASIC_LATIN
            if ch < 'A' || ('Z' < ch < 'a') || 'z' < ch
                ch = ' '
            end
        elseif block == UNICODE_LATIN_EXTENDED_B
            # normalization for Romanian
            if ch == '\u0219'
                ch = '\u015f'
            end
            if ch == '\u021b'  # Small T with comma below => with cedilla
                ch = '\u0163'
            end
        elseif block == UNICODE_GENERAL_PUNCTUATION
            ch = ' '
        elseif block == UNICODE_ARABIC
            if ch == '\u06cc'
                ch = '\u064a'  # Farsi yeh => Arabic yeh
            end
        elseif block == UNICODE_LATIN_EXTENDED_ADDITIONAL
            if ch >= '\u1ea0'
                ch = '\u1ec3'
            end
        elseif block == UNICODE_HIRAGANA
            ch = '\u3042'
        elseif block == UNICODE_KATAKANA
            ch = '\u30a2'
        elseif block in (UNICODE_BOPOMOFO, UNICODE_BOPOMOFO_EXTENDED)
            ch = '\u3105'
        elseif block == UNICODE_CJK_UNIFIED_IDEOGRAPHS
            if haskey(CJK_MAP[], string(ch))
                ch = first(CJK_MAP[][string(ch)])
            end
        elseif block == UNICODE_HANGUL_SYLLABLES
            ch = '\uac00'
        end
        push!(chars, ch)
    end
    return join(chars)
end

function clean_text(text::String; remove_url::Bool = true, remove_email::Bool = true,
                    remove_space::Bool = false, normalize::Bool = true)::String
    #= clean text
    Args:
      text: String, input text
      remove_url: Bool, whether to remove url, default true
      remove_email: Bool, whether to remove email, default true
    =#
    if remove_url
        text = replace(text, r"https?://[-_.?&~;+=/#0-9A-Za-z]{1,2076}" => "")
    end

    if remove_email
        text = replace(text, r"[-_.0-9A-Za-z]{1,64}@[-_0-9A-Za-z]{1,255}[-_.0-9A-Za-z]{1,255}" => "")
    end

    if remove_space
        text = filter(x -> !isspace(x), text)
    end

    if normalize
        text = text_normalize(text)
    end

    return text
end


text:
"""
    Utils

Module containing various utility functions for `WaveProp`.
"""
module Utils

using StaticArrays
using WavePropBase

# import all methods in WavePropBase.INTERFACE_METHODS
WavePropBase.@import_interface

export
    # Type alises
    SType,
    # methods
    svector,
    matrix_to_blockmatrix,
    blockmatrix_to_matrix,
    blockmatrix_to_matrix!,
    diagonalblockmatrix_to_matrix,
    blockvector_to_vector,
    vector_to_blockvector,
    notimplemented,
    abstractmethod,
    assert_extension,
    assert_concrete_type,
    cross_product_matrix,
    fill_zero_diagonal!,
    fill_zero_diagonal!,
    cart2sph,
    cart2pol,
    sph2cart,
    pol2cart,
    getnodes

"""
    svector(f,n)

Just like [`Base.ntuple`](https://docs.julialang.org/en/v1/base/base/#Base.ntuple), but convert output to an `SVector`.
"""
@inline svector(f,n) = ntuple(f,n) |> SVector

"""
    blockmatrix_to_matrix(A::Matrix{B}) where {B<:SMatrix}

Convert a `Matrix{B}`, where `B<:SMatrix`, to the equivalent `Matrix{T}`, where `T = eltype(B)`
"""
function blockmatrix_to_matrix(A::Matrix{B})  where B <: SMatrix
    T = eltype(B)
    sblock = size(B)
    ss     = size(A).*sblock # matrix size when viewed as matrix over T
    Afull = Matrix{T}(undef,ss)
    blockmatrix_to_matrix!(Afull,A)
    return Afull
end
function blockmatrix_to_matrix!(Afull,A::Matrix{B})  where B <: SMatrix
    sblock = size(B)
    ss     = size(A).*sblock # matrix size when viewed as matrix over T
    @assert size(Afull) == ss
    for i=1:ss[1], j=1:ss[2]
        bi, ind_i = divrem(i-1,sblock[1]) .+ (1,1)
        bj, ind_j = divrem(j-1,sblock[2]) .+ (1,1)
        Afull[i,j] = A[bi,bj][ind_i,ind_j]
    end
    return Afull
end

"""
    diagonalblockmatrix_to_matrix(A::Matrix{B}) where {B<:SMatrix}

Convert a diagonal block matrix `A::AbstractVector{B}`, where `A` is the list of diagonal blocks
and `B<:SMatrix`, to the equivalent `SparseMatrixCSC{T}`, where `T = eltype(B)`.
"""
function diagonalblockmatrix_to_matrix(A::AbstractVector{B}) where B<:SMatrix
    T = eltype(B)
    sblock = size(B)
    ss = size(A) .* sblock  # matrix size when viewed as matrix over T
    I = Int64[]
    J = Int64[]
    V = T[]
    i_full, j_full = (1, 1)
    for subA in A
        i_tmp = i_full
        for j in 1:sblock[2]
            i_full = i_tmp
            for i in 1:sblock[1]
                push!(I, i_full)
                push!(J, j_full)
                push!(V, subA[i, j])
                i_full += 1
            end
            j_full += 1
        end
    end
    return sparse(I, J, V, ss[1], ss[2])
end

"""
    blockvector_to_vector(A::Vector{B}) where {B<:SVector}

Convert a `Vector{B}`, where `B<:SVector`, to the equivalent `Vector{T}`, where `T = eltype(B)`
"""
function blockvector_to_vector(A::Vector{B})  where B <: SVector
    T = eltype(B)
    reinterpret(T,A) |> collect
end

"""
    matrix_to_blockmatrix(A::Matrix,B)

Convert a `Matrix{T}` to a `Matrix{B}`, where `B<:Type{SMatrix}`. The element
type of `B` must match that of `A`, and the size of `A` must be divisible by the
size of `B` along each dimension.
"""
function matrix_to_blockmatrix(A::Matrix,B::Type{<:SMatrix})
    @assert eltype(A) == eltype(B)
    @assert sum(size(A) .% size(B)) == 0 "block size $(size(B)) not compatible with size of A=$(size(A))"
    sblock = size(B)
    nblock = div.(size(A),sblock)
    Ablock = Matrix{B}(undef,nblock)
    for i in 1:nblock[1]
        istart = (i-1)*sblock[1] + 1
        iend = i*sblock[1]
        for j in 1:nblock[2]
            jstart = (j-1)*sblock[2] + 1
            jend   = j*sblock[2]
            Ablock[i,j] = A[istart:iend,jstart:jend]
        end
    end
    return Ablock
end

"""
    vector_to_blockvector(A::Vector,B)

Convert a `Vector{T}` to a `Vector{B}`, where `B<:Type{SVector}`. The element
type of `B` must match that of `A`, and the size of `A` must be divisible by the
size of `B` along each dimension.
"""
function vector_to_blockvector(A::Vector,B::Type{<:SVector})
    @assert eltype(A) == eltype(B)
    @assert sum(size(A) .% size(B)) == 0 "block size $(size(B)) not compatible with size of A=$(size(A))"
    reinterpret(B,A) |> collect
end

"""
    notimplemented()

Things which should probably be implemented at some point.
"""
function notimplemented()
    error("not (yet) implemented")
end

"""
    abstractmethod

A method of an `abstract type` for which concrete subtypes are expected
to provide an implementation.
"""
function abstractmethod(T::DataType)
    error("this method needs to be implemented by the concrete subtype $T.")
end
abstractmethod(x) = abstractmethod(typeof(x))

"""
    assert_extension(fname,ext,[msg])

Check that `fname` is of extension `ext`. Print the message `msg` as an assertion error otherwise.
"""
function assert_extension(fname::String,ext::String,msg="file extension must be $(ext)")
    r = Regex("$(ext)\$")
    @assert occursin(r,fname) msg
end

function assert_concrete_type(T::DataType)
    isconcretetype(T) || throw(ConcreteInferenceError(T))
end

"""
    enable_debug(mname)

Activate debugging messages.
"""
function enable_debug()
    ENV["JULIA_DEBUG"] = WavePropBase
end

struct ConcreteInferenceError <: Exception
    T
end

Base.showerror(io::IO, e::ConcreteInferenceError) = print(io, "unable to infer concrete type from function signature: T=$(e.T)" )

"""
    print_threads_info()

Prints in console the total number of threads.
"""
function print_threads_info()
    @info "Number of threads: $(Threads.nthreads())"
end

"""
    const SType{T} = Union{T,Type{T}}

Union type of `T` and its data type `Type{T}`. Used to simplify methods defined
on singleton types where both `foo(::T)` and `foo(::Type{T})` are required.
"""
const SType{T} = Union{T,Type{T}}

"""
    sort_by_type(v)

Sort the elements of `v` into vectors `vi` according to their type. Return a
`Dict{DataType,Vector}` mapping each type to a vector of that type.

# Examples
```julia
v = [1,"a",3,"b"]
dict = sort_by_type(v)
```
"""
function sort_by_type(v)
    dict = Dict{DataType,Vector}()
    for el in v
        T = typeof(el)
        vi = get!(dict,T) do
            T[]
        end
        push!(vi,el)
    end
    return dict
end

"""
    cart2sph(x,y,z)

Map cartesian coordinates `x,y,z` to spherical ones `r, θ, φ` representing the
radius, elevation, and azimuthal angle respectively. The convention followed is
that `0 ≤ θ ≤ π` and ` -π < φ ≤ π`.
"""
function cart2sph(x,y,z)
    azimuth   = atan(y,x)
    a = x^2 + y^2
    elevation = atan(sqrt(a),z)
    r = sqrt(a + z^2)
    return r, elevation, azimuth
end

"""
    sph2cart(x,y,z)

Map spherical coordinates `r,θ,φ` representing the radius, elevation, and
azimuthal angle respectively, to cartesian ones `x, y, z` .
"""
function sph2cart(r,θ,φ)
    x = r*cos(φ)*sin(θ)
    y = r*sin(φ)*sin(θ)
    z = r*cos(θ)
    return x,y,z
end

"""
    cart2pol(x,y)

Map cartesian coordinates `x,y` to polar coordinates `r,θ`. The convention
followed is that `-π ≤ θ ≤ π`.
"""
function cart2pol(x,y)
    r = sqrt(x^2 + y^2)
    θ = atan(y,x)
    return r,θ
end

"""
    pol2cart(r,θ)

Map polar coordinates `r,θ` to cartesian coordinates `x,y`.
"""
function pol2cart(r,θ)
    x = r*cos(θ)
    y = r*sin(θ)
    return x,y
end

"""
    cross_product_matrix(v)

Returns the matrix `Aᵥ` associated with the
cross product `v × ϕ` so that `v × ϕ = Aᵥϕ`.
"""
function cross_product_matrix(v)
    return transpose(SMatrix{3,3,Float64,9}(      0, -v[3],  v[2],
                                             v[3],       0, -v[1],
                                            -v[2],    v[1],     0))
end

function fill_zero_diagonal!(A::AbstractMatrix)
    # CHECK: is this function necessary?
    # it seems that, by default, the operators'
    # diagonals are always zero
    n, m = size(A)
    @assert n == m
    for i in 1:n
        A[i, i] = zero(eltype(A))
    end
end

"""
    @interface f [n=1]

Declare that the function `f` is an interface function. The call
`f(args...)` resolves to `M.f(args...)` where `M` is parent module of the
`args[n]` object.

The somewhat contrived example below illustrates how this can be used to have a
generic method defined in module `A` applied to a type defined on module `B`
which is independent of `A` but which implements the interface
function `f`:
```jldoctest
module A
    using WavePropBase.Utils
    Utils.@interface foo
    # a method which works on any type `x` implementing the `foo` function
    do_work(x) = 2*foo(x)
end

module B
    struct Foo end
    foo(x::Foo) = 1
end

using .A
using .B
foo = B.Foo()
A.do_work(foo)

# output

2
```

Note that if in the example above module `A` implements a generic version of
`foo`, the call `A.do_work(foo)` would use that method instead based on the
dispatch rules.
"""
macro interface(f,n=1)
    ex = quote
        @generated function $f(args...)
            M = parentmodule(args[$n])
            hasmethod(M.$f,args) || error("function `$(M.$f)` must be implemented in module `$M`")
            return quote
                $(M.$f)(args...)
            end
        end
    end
    return esc(ex)
end

# some reasonable defaults for coords
coords(x::Tuple)     = SVector(x)
coords(x::SVector)   = x

function coords(x::T) where {T}
    if hasfield(T,:coords)
        return getfield(x,:coords)
    else
        error("type $T has no method nor field named `coords`.")
    end
end

# some reasonable defaults for center
center(x::Tuple)     = SVector(x)
center(x::SVector)   = x

function normal(x::T) where {T}
    if hasfield(T,:normal)
        return getfield(x,:normal)
    else
        error("type $T has no method nor field named `normal`.")
    end
end

function weight(x::T) where {T}
    if hasfield(T,:weight)
        return getfield(x,:weight)
    else
        error("type $T has no method nor field named `weight`.")
    end
end

end # module

ext: jl

max_stars_repo_name: WaveProp/WavePropBase

max_issues_repo_name: WaveProp/WavePropBase

max_forks_repo_name: WaveProp/WavePropBase


text:
function add_noise(spec::Array{T,1}, snr::T) where T<:Real
    # generate noise and scale to SNR
    noise = randn(length(spec))
    scale = spec ./ std(noise)
    noise .*= (scale ./ snr)
    spec .+= noise
    return spec
end

function add_noise(spec::Array{T,2}, snr::T) where T<:Real
    # loop over each time slice adding noise
    spec_noise = zeros(size(spec))
    for t in 1:size(spec,2)
        spec_noise[:,t] = add_noise(spec[:,t], snr)
    end
    return spec_noise
end

max_stars_repo_name: palumbom/GRASS

max_issues_repo_name: palumbom/GRASS

max_forks_repo_name: palumbom/GRASS


text:
"""
    pipe_model!(node, root_value)

Computes the cross-section of `node` considering its topological environment and the
cross-section at the root node (`root_value`).

The pipe model helps compute the cross-section of the nodes in an mtg by following
the rule that the sum of the cross-sections of the children of a node is equal to
the node cross-section.

The implementation uses the following algorithm:

First, check how many children a node has.

If it has one child only, the child cross-section is equal to the node cross-section.

If more children, the node cross-section is shared between the children according to the number
of leaf nodes their subtree has, *i.e.* the total number of terminal nodes of their subtree.

Please call [`clean_cache!`](@ref) after using `pipe_model!` because it creates
temporary variables.
"""
function pipe_model!(node, root_value)
    if isroot(node)
        node[:_cache_a7118a60b2a624134bf9eac2d64f2bb32798626a] = root_value
        return root_value
    else
        parent_cross_section = node.parent[:_cache_a7118a60b2a624134bf9eac2d64f2bb32798626a]
        nleaf_node = nleaves(node)

        nleaf_proportion_siblings = nleaf_node / (sum(nleaves_siblings!(node)) + nleaf_node)

        node[:_cache_a7118a60b2a624134bf9eac2d64f2bb32798626a] = parent_cross_section * nleaf_proportion_siblings

        return node[:_cache_a7118a60b2a624134bf9eac2d64f2bb32798626a]
    end
end

# Don't forget to compute :var_name at all scales first!
# You can put all the :var_name values to be recomputed at a value < threshold_value.

"""
    pipe_model!(node, var_name, threshold_value; allow_missing = false)

Same than `pipe_model!` but uses another variable as the reference down until a threshold
value. This is used for example in the case of LiDAR measurements, where we know the
cross-section (`:var_name`) is well measured down to *e.g.* 2-3cm of diameter, but should be
computed below.

This function allows to compute the cross-section using the pipe model **only** for some
sub-trees with values of `:var_name <= threshold_value`.

# Arguments

- `node`: the mtg, or a specific node at which to start from.
- `var_name`: the name of the cross-section attribute name in the nodes
- `threshold_value`: the threshold defining the value below which the cross-section will be
re-computed using the pipe model instead of using `var_name`.
- `allow_missing=false`: Allow missing values for `var_name`, in which case the cross-section is
recomputed using the pipe model. Please use this option only if you know why.

# Details

The node cross-section is partitioned from parent to children according to the number of leaves
(*i.e.* terminal nodes) each child subtree has, unless one or more children has a
`:var_name > threshold_value`. In this case the shared cross-section is the one from the
parent minus the one of these nodes for which we simply use the measured value. The
cross-section of the siblings with `:var_name <= threshold_value` will be shared as usual
using their number of leaves. If `:var_name` of the siblings are higher than the parent value,
the cross-section of the node is computed only using the number of leaves as it should not
be bigger.

# Word of caution

Some tips when using this function:

- User must ensure that `:var_name` has a value for all nodes in the mtg before calling this
version of `pipe_model!`, unless `allow_missing=true`.

- Nodes with untrusted values should be
set to a value below the threshold value to make `pipe_model!` recompute them.
"""
function pipe_model!(node, var_name, threshold_value; allow_missing = false)

    if node[var_name] === nothing && !allow_missing
        error(
            "$var_name not found (`== nothing`) in node `$(node.name)` (id: $(node.id)). ",
            "Please make sure all nodes have a value, or use `allow_missing = true`."
        )
    end

    if isroot(node) || (node[var_name] !== nothing && node[var_name] > threshold_value)
        # The node cross-section is higher thant the threshold, we use its value
        node[:_cache_522f54c893bc239eaf0e590bda58d106f91df45d] = node[var_name]
    else
        # The parent cross-section (to share between children):
        cross_section_to_share = node.parent[:_cache_522f54c893bc239eaf0e590bda58d106f91df45d]

        node_siblings = siblings(node)

        if node_siblings === nothing || length(node_siblings) == 0
            # Test whether there are any siblings first, if not, return the parent value:
            node[:_cache_522f54c893bc239eaf0e590bda58d106f91df45d] = cross_section_to_share
        else

            cross_section_siblings = [i[var_name] for i in node_siblings]
            cross_section_siblings_no_nothing = filter(x -> x !== nothing, cross_section_siblings)

            if length(cross_section_siblings_no_nothing) > 0
                sum_cross_section_siblings = sum(cross_section_siblings_no_nothing)
            else
                sum_cross_section_siblings = 0.0
            end

            nleaf_node = nleaves(node)
            nleaves_sibl = nleaves_siblings!(node)

            # To compute how much cross-section has to be allocated to the current node, we
            # first compute how much remains after removing the measured cross-section from
            # the siblings with a cross-section > threshold_value. Then the remaining
            # cross-section is shared between all other siblings with a cross-section <= threshold_value
            # for which it is allocated respective to their number of terminal nodes (i.e. leaves):
            nleaves_others = 0
            for (i, val) in enumerate(cross_section_siblings)
                if val === nothing
                    if allow_missing
                        val = 0
                    else
                        error(
                            "$var_name not found (`== nothing`) in `$(node_siblings[i].name)`",
                            "(id: $(node_siblings[i].id)). ",
                            "Please make sure all nodes have a value, or use `allow_missing = true`."
                        )
                    end
                end

                if val > threshold_value && cross_section_to_share > val && sum_cross_section_siblings < val
                    # Here we already know the cross-section of the sibling node, so we
                    # remove its cross-section of the shareable pool, unless its cross-section
                    # is bigger than the cross-section of the parent, or also if the sum of the
                    # cross-sections of the siblings is bigger than the parent. In this case
                    # we go back to using the number of leaves because else it would give a
                    # negative value for the current node
                    cross_section_to_share -= val
                else
                    # For the others, we cumulate their number of leaves
                    nleaves_others += nleaves_sibl[i]
                end
            end

            # The cross-section remaining to share between nodes with var <= threshold_value
            # is allocated according to their proportion of leaves:
            nleaf_proportion_siblings = nleaf_node / (nleaves_others + nleaf_node)

            node[:_cache_522f54c893bc239eaf0e590bda58d106f91df45d] = cross_section_to_share * nleaf_proportion_siblings
        end
    end

    return node[:_cache_522f54c893bc239eaf0e590bda58d106f91df45d]
end


text:
module BestApproximation

export best_approx

@doc raw"""
```julia
best_approx(n::Number, R::AbstractRange)
best_approx(n::Number, R_max::Integer)
```

For a given number ``n``, given a range ``R``, finds the "nicest" solution of ``m`` such that

```math
r^m \approx n \text{ for } r \in R.
```

Returns `(r, m)` as a tuple.

Given an integer ``R_{\text{max}}`` it will assume the range between ``R = \left\lbrack 1, R_{\text{max}}\right\rbrack``.
"""
function best_approx(n::Number, R::AbstractRange)
    n, res = big(n), (0, 0)
    for i in R
        j = round(Integer, log(i, n))
        difference = (n - big(i)^(j))
        if (difference > 0) && (difference < (n - big(first(res))^(last(res))))
            res = (i, j)
        end
    end

    return res
end
best_approx(n::Number, R_max::Integer) = best_approx(n, 1:R_max)

@doc raw"""
```julia
best_approx(n::Number, I::AbstractRange, J::AbstractRange)
best_approx(n::Number, I_max::Integer, J::Integer)
```

For a given number ``n``, given two ranges ``I`` and ``J``, finds the nicest solution such that

```math
i^j \approx n \text{ for } i \in I, j \in j.
```

Returns `(i, j)` as a tuple.

Given integers ``I_{\text{max}}`` and ``J_{\text{max}}`` it will assume the ranges between ``I =  \left\lbrack 1, I_{\text{max}}\right\rbrack`` and ``J =  \left\lbrack 1, J_{\text{max}}\right\rbrack``.

!!! note

    This is a much slower variant than the other method for `best_approx`.  It is recommended that you use the "cleverer" method unless you need to constrict the exponent.
"""
function best_approx(n::Number, I::AbstractRange, J::AbstractRange)
    n, res = big(n), (0, 0)
    for i in I, j in J
        difference = (n - big(i)^(j))
        if (difference > 0) && (difference < (n - big(first(res))^(last(res))))
            res = (i, j)
        end
    end

    return res
end
best_approx(n::Number, I_max::Integer, J_max::Integer) = best_approx(n, 1:I_max, 1:J_max)

end

max_stars_repo_name: jakewilliami/BestApproximation

max_issues_repo_name: jakewilliami/BestApproximation

max_forks_repo_name: jakewilliami/BestApproximation


text:
# Note that this script can accept some limited command-line arguments, run
# `julia build_tarballs.jl --help` to see a usage message.
using BinaryBuilder, Pkg

name = "AMReX"
version = v"22.1.0"

# Collection of sources required to complete build
sources = [
    ArchiveSource("https://github.com/AMReX-Codes/amrex/releases/download/22.01/amrex-22.01.tar.gz",
                  "857df5b2fa8e3010b8856b81879a5be32ba7cc2e575474256eae7ef815b8354d"),
    DirectorySource("./bundled"),
]

# Bash recipe for building across all platforms
script = raw"""
cd $WORKSPACE/srcdir
cd amrex
atomic_patch -p1 ${WORKSPACE}/srcdir/patches/mpi-constants.patch
mkdir build
cd build
if [[ "$target" == *-apple-* ]]; then
    # Apple's Clang does not support OpenMP
    omp_opts="-DAMReX_OMP=OFF"
else
    omp_opts="-DAMReX_OMP=ON"
fi
if [[ "$target" == *-apple-* ]]; then
    # MPICH's pkgconfig file "mpich.pc" lists these options:
    #     Libs:     -framework OpenCL -Wl,-flat_namespace -Wl,-commons,use_dylibs -L${libdir} -lmpi -lpmpi -lm    -lpthread
    #     Cflags:   -I${includedir}
    # cmake doesn't know how to handle the "-framework OpenCL" option
    # and wants to use "-framework" as a stand-alone option. This fails,
    # and cmake concludes that MPI is not available.
    mpiopts="-DMPI_C_ADDITIONAL_INCLUDE_DIRS='' -DMPI_C_LIBRARIES='-Wl,-flat_namespace;-Wl,-commons,use_dylibs;-lmpi;-lpmpi' -DMPI_CXX_ADDITIONAL_INCLUDE_DIRS='' -DMPI_CXX_LIBRARIES='-Wl,-flat_namespace;-Wl,-commons,use_dylibs;-lmpi;-lpmpi'"
elif [[ "$target" == x86_64-w64-mingw32 ]]; then
    mpiopts="-DMPI_HOME=$prefix -DMPI_GUESS_LIBRARY_NAME=MSMPI -DMPI_C_LIBRARIES=msmpi64 -DMPI_CXX_LIBRARIES=msmpi64"
elif [[ "$target" == *-mingw* ]]; then
    mpiopts="-DMPI_HOME=$prefix -DMPI_GUESS_LIBRARY_NAME=MSMPI"
else
    mpiopts=
fi
cmake \
    -DCMAKE_INSTALL_PREFIX=$prefix \
    -DCMAKE_TOOLCHAIN_FILE=${CMAKE_TARGET_TOOLCHAIN} \
    -DAMReX_FORTRAN=OFF \
    -DAMReX_MPI=ON \
    -DAMReX_PARTICLES=ON \
    -DBUILD_SHARED_LIBS=ON \
    ${ompopts} \
    ${mpiopts} \
    ..
cmake --build . --config RelWithDebInfo --parallel $nproc
cmake --build . --config RelWithDebInfo --parallel $nproc --target install
"""

# These are the platforms we will build for by default, unless further
# platforms are passed in on the command line
platforms = supported_platforms()
# We cannot build with musl since AMReX requires the `fegetexcept` GNU API
platforms = filter(p -> libc(p) ≠ "musl", platforms)
platforms = expand_cxxstring_abis(platforms)

# The products that we will ensure are always built
products = [
    LibraryProduct("libamrex", :libamrex)
]

# Dependencies that must be installed before this package can be built
dependencies = [
    Dependency(PackageSpec(name="CompilerSupportLibraries_jll", uuid="e66e0078-7015-5450-92f7-15fbd957f2ae")),
    # AMReX's cmake stage fails with OpenMPI on almost all architectures
    # Dependency(PackageSpec(name="OpenMPI_jll", uuid="fe0851c0-eecd-5654-98d4-656369965a5c")),
    Dependency(PackageSpec(name="MPICH_jll"); platforms=filter(!Sys.iswindows, platforms)),
    Dependency(PackageSpec(name="MicrosoftMPI_jll"); platforms=filter(Sys.iswindows, platforms)),
]

# Build the tarballs, and possibly a `build.jl` as well.
# - GCC 4 is too old: AMReX requires C++14, and thus at least GCC 5
# - On Windows, AMReX requires C++17, and at least GCC 8 to provide the <filesystem> header.
#   How can we require this for Windows only?
# - GCC 8.1.0 suffers from an ICE, so we use GCC 9 instead
build_tarballs(ARGS, name, version, sources, script, platforms, products, dependencies;
               julia_compat="1.6", preferred_gcc_version = v"9")

max_stars_repo_name: sharanry/Yggdrasil

max_issues_repo_name: sharanry/Yggdrasil

max_forks_repo_name: sharanry/Yggdrasil


text:
"
A distribution which places all mass at value `x` of type `Float64`.

Only functions required elsewhere in the code are defined i.e. `rand`, `std`, `logpdf`.
Note `logpdf` is defined to return `1.0` or `-Inf` - this choice is to avoid `Inf-Inf` problems, rather than on a mathematical basis.
"
immutable DiracDelta <: ContinuousUnivariateDistribution
    x::Float64
end

std(d::DiracDelta) = 0.0
rand(d::DiracDelta) = d.x
logpdf(d::DiracDelta, x::Float64) = d.x == x ? 1.0 : -Inf
quantile(d::DiracDelta, x::Real) = d.x
quantile(d::DiracDelta, x::AbstractArray) = fill(d.x, size(x))


text:
baremodule MIMEFileExtensions

export fileexts_from_mime, mimes_from_fileext

function fileexts_from_mime end
function mimes_from_fileext end

function list end

"""
    fileexts_from_mime(mime::Union{Symbol,AbstractString,MIME}) -> fileexts::Vector{Symbol}

List file extentions `fileexts` known to be used for `mime`.  Return an empty tuple if not
found.

# Example

```jldoctest
julia> using MIMEFileExtensions

julia> fileexts_from_mime("image/jpeg")
3-element Vector{Symbol}:
 :jpeg
 :jpg
 :jpe

julia> fileexts_from_mime(MIME"image/png"())
1-element Vector{Symbol}:
 :png
```
"""
fileexts_from_mime

"""
    mimes_from_fileext(fileext::Union{Symbol,AbstractString}) -> mimes::Vector{Symbol}

List MIMEs `mimes` known to be used for file extention `fileext`.  Return an empty tuple if
not found.

# Example

```jldoctest
julia> using MIMEFileExtensions

julia> mimes_from_fileext("txt")
1-element Vector{Symbol}:
 Symbol("text/plain")
```
"""
mimes_from_fileext

"""
    MIMEFileExtensions.list() -> table

Return a `table`` with columns `mime::Symbol` and `fileexts::Vector{Symbol}`.

# Example

```jldoctest
julia> using MIMEFileExtensions

julia> only(
           r.mime => r.fileexts for r in MIMEFileExtensions.list()
           if r.mime == Symbol("image/jpeg")
       )
Symbol("image/jpeg") => [:jpeg, :jpg, :jpe]
```
"""
list

module Internal

using ..MIMEFileExtensions: MIMEFileExtensions

const Syms = Vector{Symbol}
const Row = NamedTuple{(:mime, :fileexts),Tuple{Symbol,Syms}}
const TABLE = Row[]

row(mime, fileexts) = Row((mime, fileexts))

datapath() = joinpath(@__DIR__, "../data/mime.types")

function load!(table = empty!(TABLE))
    lines = readlines(datapath())
    headerline = nothing
    for i in eachindex(lines)
        if startswith(lines[i], "# MIME type (lowercased)")
            headerline = i
            break
        end
    end
    if headerline === nothing
        error("header not found")
    end
    let separator = lines[headerline+1]
        if !startswith(separator, "# ===")
            error("header separator not found: ", separator)
        end
    end
    for i in headerline+2:lastindex(lines)
        ln = lines[i]
        m = match(
            r"""
            ^
            (?<comment>\#\s+)?
            (?<mime>[a-z0-9/.+\-]+)
            (?<ext>\s*[A-Za-z0-9]+.*)?
            $
            """x,
            ln,
        )
        if m !== nothing
            mime = Symbol(m["mime"])
            fileexts = map(Symbol, split(something(m["ext"], "")))
            r = row(mime, fileexts)
            push!(table, r)
        else
            error("invalid table row: ", ln)
        end
    end
    return table
end

const FILEEXTS_FROM_MIME = Dict{Symbol,Syms}()
const MIMES_FROM_FILEEXT = Dict{Symbol,Syms}()

function index_fileexts_from_mime!(dict = empty!(FILEEXTS_FROM_MIME), table = TABLE)
    for row in table
        mime = row.mime
        fileexts = row.fileexts
        isempty(fileexts) && continue
        dict[mime] = fileexts
    end
    return dict
end

function index_mimes_from_fileext!(dict = empty!(MIMES_FROM_FILEEXT), table = TABLE)
    for row in table
        mime = row.mime
        fileexts = row.fileexts
        for ext in fileexts
            push!(get!(() -> Symbol[], dict, ext), mime)
        end
    end
    return dict
end

# TODO: thread-safe lazy loading?
MIMEFileExtensions.list() = TABLE

MIMEFileExtensions.fileexts_from_mime(mime::Symbol) =
    get(FILEEXTS_FROM_MIME, mime, Symbol[])
MIMEFileExtensions.fileexts_from_mime(mime::AbstractString) =
    MIMEFileExtensions.fileexts_from_mime(Symbol(mime))
MIMEFileExtensions.fileexts_from_mime(@nospecialize(mime::MIME)) =
    MIMEFileExtensions.fileexts_from_mime(Symbol(mime))

MIMEFileExtensions.mimes_from_fileext(ext::Symbol) = get(MIMES_FROM_FILEEXT, ext, Symbol[])
MIMEFileExtensions.mimes_from_fileext(ext::AbstractString) =
    MIMEFileExtensions.mimes_from_fileext(Symbol(ext))

# Bundle `TABLE` in precompile cache for sysimage-compatibility:
load!()
include_dependency(datapath())

function __init__()
    index_fileexts_from_mime!()
    index_mimes_from_fileext!()
    return
end

@doc let path = joinpath(dirname(@__DIR__), "README.md")
    include_dependency(path)
    replace(read(path, String), r"^```julia"m => "```jldoctest README")
end MIMEFileExtensions

end  # module Internal

end  # baremodule MIMEFileExtensions


text:
function GenBuffers(count::Integer)
	a = Array(Buffer, count)
	ccall( (:glGenBuffers, lib), Void, (GLsizei, Ptr{Buffer}), count, a)
	return a
end

function GenBuffer()
	return GenBuffers(1)[1]
end

# Buffer targets
const ARRAY_BUFFER              = 0x8892
const ATOMIC_COUNTER_BUFFER     = 0x92C0
const COPY_READ_BUFFER          = 0x8F36
const COPY_WRITE_BUFFER         = 0x8F37
const DRAW_INDIRECT_BUFFER      = 0x8F3F
const DISPATCH_INDIRECT_BUFFER  = 0x90EE
const ELEMENT_ARRAY_BUFFER      = 0x8893
const PIXEL_PACK_BUFFER         = 0x88EB
const PIXEL_UNPACK_BUFFER       = 0x88EC
const SHADER_STORAGE_BUFFER     = 0x90D2
const TEXTURE_BUFFER            = 0x8C2A
const TRANSFORM_FEEDBACK_BUFFER = 0x8C8E
const UNIFORM_BUFFER            = 0x8A11

function BindBuffer(target::Integer, buf::Buffer)
	ccall( (:glBindBuffer, lib), Void, (GLenum, Buffer), target, buf)
	GetError() # TODO: Benchmark overhead
end
BindBuffer(target::Integer, buf::Integer) = BindBuffer(target, Buffer(buf))

# Buffer usage
const STREAM_DRAW  = 0x88E0
const STREAM_READ  = 0x88E1
const STREAM_COPY  = 0x88E2
const STATIC_DRAW  = 0x88E4
const STATIC_READ  = 0x88E5
const STATIC_COPY  = 0x88E6
const DYNAMIC_DRAW = 0x88E8
const DYNAMIC_READ = 0x88E9
const DYNAMIC_COPY = 0x88EA

function BufferData(target::Integer, size::Integer, data::Ptr, usage::Integer)
	ccall( (:glBufferData, lib), Void,
		(GLenum, GLsizeiptr, Ptr{GLvoid}, GLenum),
		target, size, data, usage)
	GetError()
end

BufferData{T}(target::Integer, data::Array{T}, usage::Integer) =
	BufferData(target, length(data) * sizeof(T), convert(Ptr{T}, data), usage)


text:
using Clang.Generators
using Blosc2_jll

cd(@__DIR__)

include_dir = joinpath(Blosc2_jll.artifact_dir, "include") |> normpath

options = load_options(joinpath(@__DIR__, "generator.toml"))

# add compiler flags, e.g. "-DXXXXXXXXX"
args = get_default_args()
push!(args, "-I$include_dir  -fparse-all-comments")

headers = [joinpath(include_dir, "blosc2.h")]

# create context
ctx = create_context(headers, args, options)

# run generator
build!(ctx)


text:
# extend Gumbo's text() function to work on every kind of element
function text_join{T <: HTMLNode}(ns :: Array{T})
    output = []
    for n=ns
        for e=preorder(n)
            if typeof(e) == HTMLText
                push!(output, text(e))
            end
        end
    end
    join(output, " ")
end

import Gumbo.text
text(n :: HTMLNode) = text_join([n])
text(n :: HTMLDocument) = text(n.root)
text{T <: HTMLNode}(ns :: Array{T}) = [text(n) for n=ns]

import Base.parent
parent(n :: HTMLNode) = n.parent
parent(ns :: Array{HTMLNode}) = [parent(n) for n=ns]

import Base.parse
parse{T}(t::Type{T}, n :: HTMLNode) = parse(t, text(n))
parse{T}(t::Type{T}, n :: HTMLDocument) = parse(t, text(n))


text:
@testset "Parallel Vec{$ST}" begin

  comm_rank = MPI.Comm_rank(MPI.COMM_WORLD)
  comm_size = MPI.Comm_size(MPI.COMM_WORLD)
  r = comm_rank:(comm_rank+2)
  r2 = (comm_rank+1):(comm_rank+3)
  vec1j = ST[r;]
  vec1j2 = ST[r2;]
  vec1 = PETSc.Vec(vec1j, comm=MPI.COMM_WORLD)

  @test length(vec1) == 3*comm_size
  @test vec1 == vec1j
  @test vec1 != vec1j2

  # ghost vectors
  # figure out global indices of ghost points
  nlocal = 4  # number of local values
  start_idx = 1 + comm_rank*nlocal
  end_idx = (comm_rank + 1)*nlocal
  local_range = start_idx:end_idx
  ghost_idx = [end_idx+1, end_idx+2]
  if comm_rank == (comm_size-1)  # last process
    ghost_idx = [1, 2]
  end

  v = VecGhost(Float64, nlocal, ghost_idx)
  # populate local values
  vlocal = VecLocal(v)
  for i=1:nlocal
    vlocal[i] = local_range[i]
  end

  restore(vlocal)
  ghost_update!(v)

  # check the ghost values
  vlocal = VecLocal(v)
  if comm_rank == (comm_size - 1)
    @test vlocal[nlocal + 1] == 1
    @test vlocal[nlocal + 2] == 2
  else
    @test vlocal[nlocal + 1] == end_idx + 1
    @test vlocal[nlocal + 2] == end_idx + 2
  end

  scatter!(v)
  if comm_rank == (comm_size - 1)
    @test vlocal[nlocal + 1] == 1
    @test vlocal[nlocal + 2] == 2
  else
    @test vlocal[nlocal + 1] == end_idx + 1
    @test vlocal[nlocal + 2] == end_idx + 2
  end

  vec = Vec([1.0, 2.0, 3.0])
  @test length(vec) == comm_size*3
  @test lengthlocal(vec) == 3

  vec_arr = LocalVector(vec)
  @test length(vec_arr) == 3
  restore(vec_arr)

  vec_arr = LocalVector_readonly(vec)
  @test length(vec_arr) == 3
  restore(vec_arr)

  @testset "Application Ordering{$ST}" begin

    # create an application ordering that reverses each segment a
    # vector
    vec1 = Vec(ST, vtype=PETSc.C.VECMPI, mlocal=3, comm=MPI.COMM_WORLD)
    local_range = localpart(vec1)

    start_idx = local_range[1]
    end_idx = local_range[end]
    app_idx = collect(PetscInt, end_idx:-1:start_idx)
    petsc_idx = collect(PetscInt, local_range)

    ao = AO(ST, app_idx, petsc_idx)

    # transform back
    idx_arr = copy(petsc_idx)
    map_petsc_to_app!(ao, idx_arr)
    @test idx_arr == app_idx

    # transform back
    map_app_to_petsc!(ao, idx_arr)
    @test idx_arr == petsc_idx

    # do the same for index sets
    is_petsc = IS(ST, petsc_idx, comm=MPI.COMM_WORLD)
    is_app = IS(ST, app_idx, comm=MPI.COMM_WORLD)
    ao2 = AO(is_app, is_petsc)

    working_is = copy(is_petsc)
    map_petsc_to_app!(ao2, working_is)
    @test working_is == is_app

    map_app_to_petsc!(ao2, working_is)
    @test working_is == is_petsc

  end
end


text:
const TupleOfModels = Union{NTuple{N, MillModel} where {N}, NamedTuple}

"""
    struct ProductModel{N, T <: MillFunction} <: MillModel
        ms::NTuple{N, MillModel}
        m::ArrayModel{T}
    end

    uses each model in `ms` on each data in `TreeNode`, concatenate the output and pass it to the chainmodel `m`
"""
struct ProductModel{TT<:TupleOfModels, T <: MillFunction} <: MillModel
    ms::TT
    m::ArrayModel{T}
end

Flux.@treelike ProductModel
# Flux.@functor ProductModel

ProductModel(ms::TT) where {TT<:TupleOfModels} = ProductModel(ms, ArrayModel(identity))
ProductModel(ms, f::MillFunction) = ProductModel(ms, ArrayModel(f))

function (m::ProductModel{MS,M})(x::TreeNode{P,T}) where {P<:Tuple,T,MS<:Tuple, M}
    xx = vcat([m.ms[i](x.data[i]) for i in 1:length(m.ms)]...)
    m.m(xx)
end

function (m::ProductModel{MS,M})(x::TreeNode{P,T}) where {P<:NamedTuple,T,MS<:NamedTuple, M}
    xx = vcat([m.ms[k](x.data[k]) for k in keys(m.ms)]...)
    m.m(xx)
end

function modelprint(io::IO, m::ProductModel; pad=[], s="", tr=false)
    c = COLORS[(length(pad)%length(COLORS))+1]
    paddedprint(io, "ProductModel$(tr_repr(s, tr)) (", color=c)

    n = length(m.ms)
    ks = key_labels(m.ms)
    for i in 1:(n-1)
        println(io)
        paddedprint(io, "  ├── $(ks[i])", color=c, pad=pad)
        modelprint(io, m.ms[i], pad=[pad; (c, "  │   ")], s=s * encode(i, n), tr=tr)
    end
    println(io)
    paddedprint(io, "  └── $(ks[end])", color=c, pad=pad)
    modelprint(io, m.ms[end], pad=[pad; (c, "      ")], s=s * encode(n, n), tr=tr)

    println(io)
    paddedprint(io, " ) ↦  ", color=c, pad=pad)
    modelprint(io, m.m, pad=[pad; (c, "")])
end


text:
abstract type AbstractLiklihood end

struct Gaussian <:AbstractLiklihood end

max_stars_repo_name: biobakery/GaPLAC

max_issues_repo_name: biobakery/GaPLAC

max_forks_repo_name: biobakery/GaPLAC


text:
export truncate!

function truncate!(P::Vector{Float64}; kwargs...)::Tuple{Float64,Float64}
  # Keyword argument deprecations
  use_absolute_cutoff = false
  if haskey(kwargs, :absoluteCutoff)
    @warn "In truncate!, keyword argument absoluteCutoff is deprecated in favor of use_absolute_cutoff"
    use_absolute_cutoff = get(kwargs, :absoluteCutoff, use_absolute_cutoff)
  end
  use_relative_cutoff = true
  if haskey(kwargs, :doRelCutoff)
    @warn "In truncate!, keyword argument doRelCutoff is deprecated in favor of use_relative_cutoff"
    use_relative_cutoff = get(kwargs, :doRelCutoff, use_relative_cutoff)
  end

  maxdim::Int = min(get(kwargs, :maxdim, length(P)), length(P))
  mindim::Int = max(get(kwargs, :mindim, 1), 1)
  cutoff::Float64 = max(get(kwargs, :cutoff, 0.0), 0.0)
  use_absolute_cutoff::Bool = get(kwargs, :use_absolute_cutoff, use_absolute_cutoff)
  use_relative_cutoff::Bool = get(kwargs, :use_relative_cutoff, use_relative_cutoff)

  origm = length(P)
  docut = 0.0

  #if P[1] <= 0.0
  #  P[1] = 0.0
  #  resize!(P, 1)
  #  return 0.0, 0.0
  #end

  if origm == 1
    docut = abs(P[1]) / 2
    return 0.0, docut
  end

  s = sign(P[1])
  s < 0 && (P .*= s)

  #Zero out any negative weight
  for n in origm:-1:1
    (P[n] >= 0.0) && break
    P[n] = 0.0
  end

  n = origm
  truncerr = 0.0
  while n > maxdim
    truncerr += P[n]
    n -= 1
  end

  if use_absolute_cutoff
    #Test if individual prob. weights fall below cutoff
    #rather than using *sum* of discarded weights
    while P[n] <= cutoff && n > mindim
      truncerr += P[n]
      n -= 1
    end
  else
    scale = 1.0
    if use_relative_cutoff
      scale = sum(P)
      (scale == 0.0) && (scale = 1.0)
    end

    #Continue truncating until *sum* of discarded probability
    #weight reaches cutoff reached (or m==mindim)
    while (truncerr + P[n] <= cutoff * scale) && (n > mindim)
      truncerr += P[n]
      n -= 1
    end

    truncerr /= scale
  end

  if n < 1
    n = 1
  end

  if n < origm
    docut = (P[n] + P[n + 1]) / 2
    if abs(P[n] - P[n + 1]) < 1E-3 * P[n]
      docut += 1E-3 * P[n]
    end
  end

  s < 0 && (P .*= s)
  resize!(P, n)

  return truncerr, docut
end


text:
using ITensors.NDTensors

using ITensors.NDTensors: AllowAlias

function rrule(f::Type{<:Tensor}, x1::AllowAlias, x2::TensorStorage, x3::Tuple)
  y = f(x1, x2, x3)
  function Tensor_pullback(ȳ)
    x̄1 = NoTangent()
    x̄2 = ȳ.storage
    x̄3 = NoTangent()
    return (NoTangent(), x̄1, x̄2, x̄3)
  end
  return y, Tensor_pullback
end

function rrule(::typeof(tensor), x1::TensorStorage, x2::Tuple)
  y = tensor(x1, x2)
  function tensor_pullback(ȳ)
    x̄1 = storage(ȳ)
    x̄2 = NoTangent()
    return (NoTangent(), x̄1, x̄2)
  end
  return y, tensor_pullback
end

function rrule(f::Type{<:Tensor}, x1::TensorStorage, x2::Tuple)
  y = f(x1, x2)
  function tensor_pullback(ȳ)
    x̄1 = copy(storage(x1))
    x̄2 = NoTangent()
    return (NoTangent(), x̄1, x̄2)
  end
  return y, tensor_pullback
end


text:
module BookExamples

using PotentialFlow

using PyPlot

import PotentialFlow.Utils:@get, MappedVector

import PyCall

export install_examples, plot_streamlines, conformal_grid

const mygreen = [151/255,180/255,118/255];
const mygreen2 = [113/255,161/255,103/255];
const myblue = [74/255,144/255,226/255];

PlotColor = Union{Vector{Float64},String,Symbol}

struct PlotStyle

  bodycolor :: PlotColor
  xicolor   :: PlotColor
  etacolor  :: PlotColor
  markercolor :: PlotColor
  linewidth :: Int64
  pointsize :: Int64

end

function install_examples(dir::String)

  fulldir = joinpath(homedir(),dir)
  if !isdir(fulldir)
      mkdir(fulldir)
  end

  examplesroot = joinpath(Pkg.dir("BookExamples"),"binder/notebooks")
  for (root, dirs, files) in walkdir(examplesroot)
    for file in files
        if contains(file[end-5:end], ".ipynb") && root == examplesroot
            cp(joinpath(root, file),joinpath(fulldir,file)) # path to files
        end
    end
  end

end

function PlotStyle()
  PyCall.PyDict(matplotlib["rcParams"])["font.family"] = "STIXGeneral";
  PyCall.PyDict(matplotlib["rcParams"])["mathtext.fontset"] = "stix";
  PyCall.PyDict(matplotlib["rcParams"])["font.size"] = 10;

  bodycolor = mygreen
  xicolor = mygreen2
  etacolor = myblue
  markercolor = "r"
  linewidth = 1
  pointsize = 3

  PlotStyle(bodycolor,xicolor,etacolor,markercolor,linewidth,pointsize)

end

function conformal_grid(b::Bodies.PowerBody)

  ps = PlotStyle()

  xmaxc = 4.0
  rmax = sqrt(2)*xmaxc

  PyPlot.subplot(121)
  PyPlot.axis("scaled")
  PyPlot.axis([-xmaxc,xmaxc,-xmaxc,xmaxc])
  PyPlot.xticks(ceil(-xmaxc/2)*2:2:floor(xmaxc/2)*2)
  PyPlot.yticks(ceil(-xmaxc/2)*2:2:floor(xmaxc/2)*2)

  PyPlot.subplot(122)
  PyPlot.axis("scaled")
  xmaxp = xmaxc*abs(b.C[1])
  dxp = 0.5*xmaxp
  PyPlot.axis([-xmaxp,xmaxp,-xmaxp,xmaxp])
  PyPlot.xticks(ceil(-xmaxp/dxp)*dxp:dxp:floor(xmaxp/dxp)*dxp)
  PyPlot.yticks(ceil(-xmaxp/dxp)*dxp:dxp:floor(xmaxp/dxp)*dxp)

  nθ = 20
  dθ = 2π/nθ
  θg = 0
  nrg = 100
  rg = linspace(1,rmax,nrg)
  for jθ in 1:nθ
      ζg = collect(rg*exp(im*θg))
      zg = Bodies.powermap(ζg,b.C)

       PyPlot.subplot(121)
       PyPlot.plot(real(ζg),imag(ζg),linewidth=ps.linewidth,color=ps.xicolor)

       PyPlot.subplot(122)
       PyPlot.plot(real(zg),imag(zg),linewidth=ps.linewidth,color=ps.xicolor)

       θg += dθ
   end

   nr = 10
   dr = (rmax-1)/nr
   rg = 1

   nθg = 200
   θg = linspace(0,2π,nθg)
   #if strcmpi(maptype,'sc')
  #     thg = [thg shape.prevangle'];  % Include prevertices
  #     thsort = sort(thg - floor(thg/(2*pi))*2*pi);  % Normalize the range
    #   thg = thsort(abs(diff(thsort))>1e-15);  % Eliminate duplicates
  #     thg = [thg 2*pi];
   #end
   for jr in 1:nr
       ζg = collect(rg*exp.(im*θg));
       zg = Bodies.powermap(ζg,b.C)

       PyPlot.subplot(121)
       PyPlot.plot(real(ζg),imag(ζg),linewidth=ps.linewidth,color=ps.etacolor)

       PyPlot.subplot(122)
       PyPlot.plot(real(zg),imag(zg),linewidth=ps.linewidth,color=ps.etacolor)

       rg += dr
   end

end

function setup_grid()

  len = 1;
  xcent = 0; ycent = 0;
  xfact = 6; yfact = 6;
  xlen = xfact*len; ylen = yfact*len;
  xmin = xcent - 0.5*xlen; xmax = xcent + min(0.5*xlen,len);
  ymin = ycent - min(0.5*ylen,2*len);
  ymax = ycent + min(0.5*ylen,2*len);

  nth = 400;
  dth = 2π/nth;
  θ = linspace(dth,2π+dth,nth+1);
  dr = dth;
  r = [dr];
  while maximum(r) < xlen
    push!(r,r[end]+dr)
    dr = r[end]*dth;
  end
  xmin = -maximum(r)/sqrt(2); xmax = maximum(r)/sqrt(2);
  ymin = -maximum(r)/sqrt(2); ymax = maximum(r)/sqrt(2);
  Z = r * exp.(im*θ');

  PyPlot.axis("scaled");
  PyPlot.axis([xmin,xmax,ymin,ymax]);
  PyPlot.xticks(ceil(xmin/2)*2:2:floor(xmax/2)*2);
  PyPlot.yticks(ceil(ymin/2)*2:2:floor(ymax/2)*2);

  Z

end

function setup_grid(b::Bodies.PowerBody)

  eps = 0.00001;
  len = 1/abs(b.C[1]);
  xcent = 0; ycent = 0;
  xfact = 6; yfact = 6;
  xlen = xfact*len; ylen = yfact*len;
  xmin = xcent - 0.5*xlen; xmax = xcent + min(0.5*xlen,len);
  ymin = ycent - min(0.5*ylen,2*len);
  ymax = ycent + min(0.5*ylen,2*len);

  nth = 400;
  dth = 2π/nth;
  θ = linspace(0,2π,nth+1);
  dr = dth;
  r = [1+eps];
  while maximum(r) < xlen
    push!(r,r[end]+dr)
    dr = r[end]*dth;
  end
  xmin = -maximum(r)/sqrt(2)*abs(b.C[1]); xmax = maximum(r)/sqrt(2)*abs(b.C[1]);
  ymin = -maximum(r)/sqrt(2)*abs(b.C[1]); ymax = maximum(r)/sqrt(2)*abs(b.C[1]);
  ζ = r * exp.(im*θ');
  Z = zero(ζ);
  Z = [Bodies.powermap(ζi,b.C) for ζi in ζ];

  PyPlot.axis("scaled");
  PyPlot.axis([xmin,xmax,ymin,ymax]);
  PyPlot.xticks(ceil(xmin/2)*2:2:floor(xmax/2)*2);
  PyPlot.yticks(ceil(ymin/2)*2:2:floor(ymax/2)*2);

  Z, ζ

end

function set_contour_levels(ψ,vmin::Float64,vmax::Float64,n::Int64)

  vmean = vmax-vmin;
  vfact = 1;
  vmin = vmean+vfact*(vmin-vmean);
  vmax = vmean+vfact*(vmax-vmean);
  v = linspace(vmin,vmax,n);
  return v

end

function set_contour_levels(ψ)

  vmin = minimum(ψ);
  vmax = maximum(ψ);
  v = set_contour_levels(ψ,vmin,vmax,31)
  if (sign(vmin) != sign(vmax))
    v = v .- v[abs.(v).==minimum(abs.(v))];
  end
  return v

end

function plot_streamlines(a::Vector{Complex128})

  ps = PlotStyle()
  Z = setup_grid()

  F = a[1]*Z
  if length(a) > 1
    F += a[2]*log.(Z)
  end
  Z⁻ⁿ = 1./Z
  for j = 3:length(a)
    F += a[j]*Z⁻ⁿ
    Z⁻ⁿ ./= Z
  end
  ψ = imag(F)

  ψmin = minimum(ψ[abs.(Z).>1.0])
  ψmax = maximum(ψ[abs.(Z).>1.0])

  pc = PyPlot.contour(real(Z),imag(Z),ψ,
                      set_contour_levels(ψ,ψmin,ψmax,21),
                      colors="black",linestyles="solid",linewidths=ps.linewidth);

end

function plot_streamlines(b::Bodies.PowerBody,m::Bodies.RigidBodyMotion,t)

  ps = PlotStyle()

  Z, ζ = setup_grid(b)

  ψ = zero(ζ);
  ψ = Bodies.streamfunction.(ζ,b,m,t)

  pc = PyPlot.contour(real(Z),imag(Z),ψ,set_contour_levels(ψ),
                      colors="black",linestyles="solid",linewidths=ps.linewidth);
  pf = PyPlot.fill(real(b.zs),imag(b.zs),facecolor=ps.bodycolor);
  PyPlot.plot(real(b.zs),imag(b.zs),color=ps.bodycolor);

  #PyPlot.savefig("/Users/jeff/Desktop/testfig2.eps",format="eps")

end

function plot_streamlines(b::Bodies.PowerBody,src,t)

  ps = PlotStyle()

  Z, ζ = setup_grid(b)

  ψ = [Bodies.streamfunction(ζi,b,src,t) for ζi in ζ]

  pc = PyPlot.contour(real(Z),imag(Z),ψ,set_contour_levels(ψ),
                      colors="black",linestyles="solid",linewidths=ps.linewidth);
  pf = PyPlot.fill(real(b.zs),imag(b.zs),facecolor=ps.bodycolor);
  PyPlot.plot(real(b.zs),imag(b.zs),color=ps.bodycolor);

  z = Elements.conftransform(src,b);
  PyPlot.plot(real.(z),imag.(z),
              marker="o",markerfacecolor=ps.markercolor,markersize=ps.pointsize,
              markeredgecolor=ps.markercolor,linestyle="none");

  #PyPlot.savefig("/Users/jeff/Desktop/testfig2.eps",format="eps")

end

function plot_streamlines(b::Bodies.PowerBody,Winf::Complex128)

  ps = PlotStyle()

  Z, ζ = setup_grid(b)

  ψ = zero(ζ);
  ψ = Bodies.streamfunction.(ζ,b,Winf,0.0)

  pc = PyPlot.contour(real(Z),imag(Z),ψ,set_contour_levels(ψ),
                      colors="black",linestyles="solid",linewidths=ps.linewidth);
  pf = PyPlot.fill(real(b.zs),imag(b.zs),facecolor=ps.bodycolor);
  PyPlot.plot(real(b.zs),imag(b.zs),color=ps.bodycolor);

  #PyPlot.savefig("/Users/jeff/Desktop/testfig2.eps",format="eps")

end

end

max_stars_repo_name: jdeldre/BookExamples

max_issues_repo_name: jdeldre/BookExamples

max_forks_repo_name: jdeldre/BookExamples


text:
export execute_chromosome, concatenate_outputs, create_count_function_list, increment_count_function_list, print_function_list
export evaluate_node, create_count_function_hash, increment_count_function_hash, print_count_function_hash, print_count_function_summary
export execute_chromosome_ft, evaluate_node_ft
using Printf

# Limits Funcs to 4 inputs
# TODO:  Write this function generally
function apply( f::Function,args)
  if length(args) == 0
    f()
  elseif length(args) == 1
    f(args[1])
  elseif length(args) == 2
    f(args[1],args[2])
  elseif length(args) == 3
    f(args[1],args[2],args[3])
  elseif length(args) == 4
    f(args[1],args[2],args[3],args[4])
  end
end

function evaluate_node(c::Chromosome, node::InputNode, context::Vector)
    #println("eval input node: ",node)
    #print("context[node.index]:")
    #Printf.@printf("0x0%4x\n",context[node.index])
    #=
    if node.active
      return node.cache
    end
    node.active = true
    node.cache = context[node.index]
    =#
    return context[node.index]
end

# Restored caching on 10/7/21
function evaluate_node(c::Chromosome, node::InteriorNode, context::Vector)
    #println("en eval interior node: ",node,"  contxt: ",context)
    prev_cache = node.cache
    func = node.func
    if node.active
      return node.cache
    end
    #println("node.inputs: ",node.inputs)
    args = map(node.inputs[1:func.arity]) do index
        #println("index: ",index)
        evaluate_node(c, c[index], context)
    end
    #args = map(x->context[x],node.inputs)
    #println("args: ",args)
    result = CGP.apply(func.func, args)
    #print("func: ",func,"  args: ",args,"  result: ")
    #Printf.@printf("0x%2x\n",result)
    if node.active
      @assert result == node.cache
    end
    node.active = true
    node.cache = result
    return result
end

# Evaluate an interior node in isolation using context as the input
# context should be the context for numinputs.
function eval_interior( node::InteriorNode, context::Vector )
    println("ei eval interior node: ",node)
    func = node.func
    args = [context[i] for i = 1:func.arity]
    #println("args: ",args)
    Main.CGP.apply(func.func, args)
end

function evaluate_node(c::Chromosome, node::OutputNode, context::Vector)
    index = node.input
    result = evaluate_node(c, c[index], context)
    #println("eval output node: ",node,"  index: ",index,"  result: ",result)
    # result = evaluate_node(c, c[index], context)
    return result
end

# Execute chromosme using context for the inputs and return outputs
function execute_chromosome(c::Chromosome, context::Vector; permute_context::Bool=false )
    #println("Executing chromosome")
    #print_chromosome(c)
    #println("context: ",context)
    return [evaluate_node(c, node, context) for node = c.outputs]
end

function evaluate_node_ft(c::Chromosome, node::OutputNode, context::Vector, perturb_index::Int64 )
    index = node.input
    #println("ft eval output node: ",node,"  index: ",index)
    if index == perturb_index
        #println("ft out index == perturb_index")
        result = NOT.func(evaluate_node_ft(c, c[index], context,perturb_index))
    else
       result = evaluate_node_ft(c, c[index], context,perturb_index)
    end
    #print("ft out index: ",index)
    #Printf.@printf("ft out result: 0x%2x\n",result)
    return result
end

function evaluate_node_ft(c::Chromosome, node::InputNode, context::Vector, perturb_index::Int64 )
    #println("ft eval input node: ",node)
    #Printf.@printf("ft result:  0x0%4x\n",context[node.index])
    return context[node.index]
end

function evaluate_node_ft(c::Chromosome, node::InteriorNode, context::Vector, perturb_index::Int64 )
    #println("ft eval interior node: ",node)
    func = node.func
    args = map(node.inputs[1:func.arity]) do index
        #println("index: ",index)
        if index == perturb_index
          #println("  ft int index == perturb_index")
          NOT.func(evaluate_node_ft(c, c[index], context,perturb_index))
        else
          evaluate_node_ft(c, c[index], context,perturb_index)
        end
    end
    result = apply(func.func, args)
    #print("ft int func: ",func,"  args: ",args )
    #Printf.@printf("  ft int result: 0x%2x\n",result)
    return result
end

# Fault tolerant version where the output of node c[perturb_index] is negated
# Note: ignores cache values for nodes
function execute_chromosome_ft(c::Chromosome, context::Vector, perturb_index::Int64 )
    #println("FT Executing chromosome")
    #print_chromosome(c)
    return [evaluate_node_ft(c, node, context,perturb_index) for node = c.outputs]
end

function print_function_list( function_list::Vector{MyFunc} )
  for i = convert(MyFunc,1):length(function_list)
    if function_list[i] > 0
      # The correct way to print when MyInt=UInt16, but wont work when MyInt is something else
      #Printf.@printf("0x%04x  %d\n",i-1,function_list[i])
    end
  end
end

# The purpose of the remaining functions in this file is to
#      generate random circuits and count the logic functions that are computed.
# Similar to results of Raman and Wagner 2011 to generate their Figure 2b.
function create_count_function_list( numinputs::Integer, numoutputs::Integer )
  return fill( convert(MyFunc,0), 2^(numoutputs*2^numinputs ))
end

function increment_count_function_list( funct::MyFunc, function_list::Vector{MyFunc} )
  function_list[funct+1] += 1
end

function create_count_function_hash( numinputs::Integer, numoutputs::Integer )
  Dict{MyFunc,Int64}()
end

function increment_count_function_hash( funct::MyFunc, counts::Dict{MyFunc,Int64} )
  counts[funct] = get!( counts, funct, 0 ) + 1
end

function concatenate_outputs( numinputs::Integer, outputs::Vector{Main.CGP.MyInt}  )
  shift = 2^numinputs
  result = convert(MyFunc,outputs[1])
  for i = 2:length(outputs)
    result = convert(MyFunc,outputs[i]) | result << 2^numinputs
  end
  result
end

function print_count_function_hash( f::IOStream, counts::Dict{MyFunc,Int64} )
  length_counts_list_max_for_printing = 50
  println(f,"function count")
  counts_list = [(k,counts[k]) for k in keys(counts) ]
  sort!( counts_list, by=x->x[2], rev=true )
  len = length(counts_list)
  sum = 0
  for c in counts_list[1:len]
    #=
    if len <= length_counts_list_max_for_printing
      Printf.@printf(f,"0x%04x   %d\n",c[1],c[2])
    end
    =#
    sum += c[2]
  end
  println(f,"length counts list: ",len,"  sum: ",sum)
end

function print_count_function_hash( counts::Dict{MyFunc,Int64} )
  print_count_function_hash( Base.stdout, counts )
end

function print_count_function_summary( f::IOStream,  counts::Dict{MyFunc,Int64} )
  length_print_count_max_for_printing = 150
  println(f,"count function hash: ")
  counts_list = [(k,counts[k]) for k in keys(counts) ]
  sort!( counts_list, by=x->x[2] )
  current_count = counts_list[1][2]
  accum_count = 1
  accum_sum = 0
  print_count = 0
  println(f,"funct freq  count")
  for i = 2:length(counts_list)
    if counts_list[i][2] == current_count
      accum_count += 1
    else
      if print_count <= length_print_count_max_for_printing
        println(f,"     ",current_count,":      ",accum_count)
      end
      print_count += 1
      accum_sum += current_count*accum_count
      current_count = counts_list[i][2]
      accum_count = 1
    end
  end
  accum_sum += current_count*accum_count
  if print_count <= length_print_count_max_for_printing
    println(f,"     ",current_count,":      ",accum_count)
  end
  println(f,"print_count: ",print_count,"  accum_sum: ",accum_sum)
end

function print_count_function_summary( counts::Dict{MyFunc,Int64} )
  print_count_function_summary( f, counts )
end


text:
function decode(::Type{NutationCodec}, arg0::jlong)
    return jcall(NutationCodec, "decode", Vector{jint}, (jlong,), arg0)
end

function encode(::Type{NutationCodec}, arg0::Vector{jint})
    return jcall(NutationCodec, "encode", jlong, (Vector{jint},), arg0)
end

function get_key(obj::NutationCodec)
    return jcall(obj, "getKey", jlong, ())
end


text:
@testset "testers.jl" begin
    @testset "test_scalar" begin
        double(x) = 2x
        @scalar_rule(double(x), 2)
        test_scalar(double, 2)
    end

    @testset "unary: identity(x)" begin
        function ChainRulesCore.frule((_, ẏ), ::typeof(identity), x)
            return x, ẏ
        end
        function ChainRulesCore.rrule(::typeof(identity), x)
            function identity_pullback(ȳ)
                return (NO_FIELDS, ȳ)
            end
            return x, identity_pullback
        end
        @testset "frule_test" begin
            frule_test(identity, (randn(), randn()))
            frule_test(identity, (randn(4), randn(4)))
        end
        @testset "rrule_test" begin
            rrule_test(identity, randn(), (randn(), randn()))
            rrule_test(identity, randn(4), (randn(4), randn(4)))
        end
    end

    @testset "binary: fst(x, y)" begin
        fst(x, y) = x
        ChainRulesCore.frule((_, dx, dy), ::typeof(fst), x, y) = (x, dx)
        function ChainRulesCore.rrule(::typeof(fst), x, y)
            function fst_pullback(Δx)
                return (NO_FIELDS, Δx, Zero())
            end
            return x, fst_pullback
        end
        @testset "frule_test" begin
            frule_test(fst, (2, 4.0), (3, 5.0))
            frule_test(fst, (randn(4), randn(4)), (randn(4), randn(4)))
        end
        @testset "rrule_test" begin
            rrule_test(fst, rand(), (2.0, 4.0), (3.0, 5.0))
            rrule_test(fst, randn(4), (randn(4), randn(4)), (randn(4), randn(4)))
        end
    end

    @testset "tuple input: first" begin
        ChainRulesCore.frule((_, dx), ::typeof(first), xs::Tuple) = (first(xs), first(dx))
        function ChainRulesCore.rrule(::typeof(first), x::Tuple)
            function first_pullback(Δx)
                return (NO_FIELDS, Composite{typeof(x)}(Δx, falses(length(x)-1)...))
            end
            return first(x), first_pullback
        end

        CTuple{N} = Composite{NTuple{N, Float64}}  # shorter for testing
        @testset "frule_test" begin
            frule_test(first, ((2.0, 3.0), CTuple{2}(4.0, 5.0)))
            frule_test(first, (Tuple(randn(4)), CTuple{4}(randn(4)...)))
        end
        @testset "rrule_test" begin
            rrule_test(first, 2.0, ((2.0, 3.0), CTuple{2}(4.0, 5.0)))
            rrule_test(first, randn(), (Tuple(randn(4)), CTuple{4}(randn(4)...)))
        end
    end
end


text:
#
# tests of json3 serialisation of a struct.
# see: https://github.com/quinnj/JSON3.jl
#    : https://github.com/JuliaData/StructTypes.jl
#
using JSON3
using StructTypes
using TimeSeries

@enum Fred fred joe

DD = Dict{String,Real}

struct X
    i :: Int
end

struct T7{T}
     t :: Vector{X}
     x :: T
     f :: Fred
     d :: DD
end

StructTypes.StructType(::Type{X}) = StructTypes.Struct()
StructTypes.StructType(::Type{T7{Int64}}) = StructTypes.Struct()
StructTypes.StructType(::Type{T7{Float64}}) = StructTypes.Struct()

t = T7{Float64}( [X(9), X(8)], 77.0, joe, Dict("A"=>1,"B"=>2 ))

s = JSON3.write( t )

t2 = JSON3.read( s, T7{Float64} )

t2 == t

## todo Pretty

io = open("/tmp/t7.json", "w")
JSON3.pretty( io, s )
close( io )

io = open("/tmp/t7.json", "r")
t3 = JSON3.read( io, T7{Float64} )
close( io )

using ScottishTaxBenefitModel
using .GeneralTaxComponents: RateBands, WEEKS_PER_YEAR
using .Definitions
using Parameters

using .STBParameters

StructTypes.StructType(::Type{IncomeTaxSys{Float64}}) = StructTypes.Struct()
it = IncomeTaxSys{Float64}()

s = JSON3.write( it )

io = open("/tmp/itsys.json", "w")
JSON3.pretty( io, s )
close( io )

io = open("/tmp/itsys.json", "r")
t3 = JSON3.read( io, IncomeTaxSys{Float64} )
close( io )

module M

    using JSON3
    using StructTypes

    struct X1
        i :: Int
    end
    StructTypes.StructType(::Type{X1}) = StructTypes.Struct()

    DD2 = Dict{String,Real}

    @enum Fred2 fred2 joe2

    struct T8{T}
         t :: Vector{X1}
         x :: T
         f :: Fred2
         d :: DD2
    end

    StructTypes.StructType(::Type{T8{Float64}}) = StructTypes.Struct()

    function to_file( filename :: String, t :: T8 )
        io = open( filename, "w")
        JSON3.write( io, t )
        close( io )
    end

    function from_file( filename :: String )::T8
        io = open( filename, "r")
        t = JSON3.read( io, T8{Float64} )
        close( io )
        return t
    end

    t8 = T8{Float64}( [X1(9), X1(8)], 77.0, fred2, Dict("A"=>1,"B"=>2 ))

end # m

M.to_file( "/tmp/t8.json", M.t8 )
l_t8 = M.from_file( "/tmp/t8.json" )

module N

    struct X{T<:Number}
      a :: T
    end

end

#
# this one always works but has an ugly global variable
# I'm using this for now.
#
module O

    using JSON3
    using StructTypes

    T = Int

    # StructTypes.StructType(::Type{Main.N.X{<:Number}}) = StructTypes.Struct() # doesn't work

    StructTypes.StructType(::Type{Main.N.X{T}}) = StructTypes.Struct()

    function to_file( filename :: String, t :: Main.N.X )
        io = open( filename, "w")
        JSON3.write( io, t )
        close( io )
    end

    function from_file( filename :: String )::Main.N.X
        io = open( filename, "r")
        t = JSON3.read( io, Main.N.X{T} )
        close( io )
        return t
    end

end

#
# this one sort of works and is much neater but
# fails with non-standard types like `Float32`
#
module P

    using JSON3
    using StructTypes

    function to_file( filename :: String, t :: Main.N.X{T} ) where T
        if T !== Float64
            @eval begin
                StructTypes.numbertype(::Type{$T}) = StructTypes.NumberType()
            end
        end
        @eval begin
            StructTypes.StructType(::Type{Main.N.X{$T}}) = StructTypes.Struct()
        end
        io = open( filename, "w")
        JSON3.write( io, t )
        close( io )
    end

    function from_file( filename :: String, T::Type )::Main.N.X
        io = open( filename, "r")
        #if T not in (Float64,Int)
              @eval begin
                   StructTypes.numbertype( ::Type{$T}) = StructTypes.NumberType()
              end
         #  end

        t = JSON3.read( io, Main.N.X{T} )
        close( io )
        return t
    end
end

module Q

    using JSON3
    using StructTypes

    function to_file( filename :: String, t :: Main.N.X )
        T = typeof( t.a )
        @eval begin
            StructTypes.StructType(::Type{Main.N.X}) = StructTypes.Struct()
            StructTypes.StructType(::Type{Main.N.X{$T}}) = StructTypes.Struct()
        end
        io = open( filename, "w")
        JSON3.write( io, t )
        close( io )
    end

end

module R
    using TimeSeries
    using JSON3
    using StructTypes

    data = (datetime = [DateTime(2018, 11, 21, 12, 0), DateTime(2018, 11, 21, 13, 0)],
        col1 = [10.2, 11.2],
        col2 = [20.2, 21.2],
        col3 = [30.2, 31.2])
    ta = TimeArray(data; timestamp = :datetime, meta = "Example")
    StructTypes.StructType(::Type{TimeArray{Float64,2,DateTime,Array{Float64,2}}}) = StructTypes.Struct()

end

typeof(R.ta)

s = JSON3.write( R.ta )
ta2 = JSON3.read( s, TimeArray{Float64,2,DateTime,Array{Float64,2}})

max_stars_repo_name: grahamstark/ScottishTaxBenefitModel

max_issues_repo_name: grahamstark/ScottishTaxBenefitModel

max_forks_repo_name: grahamstark/ScottishTaxBenefitModel


text:
using Test

@testset "Graphviz" begin
  include("Graphviz.jl")
  include("GraphvizWiringDiagrams.jl")
end

@testset "TikZ" begin
  include("TikZ.jl")
  include("TikZWiringDiagrams.jl")
end


text:
# Tests for pipeline.jl

# test_tmscore_path is defined in runtests.jl
test_i1 = testfile("1CLL_H.pdb")
test_d1 = testfile("1CLL.dssp")
test_i2 = testfile("1CTR_H.pdb")
test_d2 = testfile("1CTR.dssp")
test_extra_pdb_1 = testfile("1CFF_1.pdb")
test_extra_pdb_2 = testfile("1CFF_2.pdb")
test_pocket_points = testfile("1CTR_pocket_points.pdb")

# Increasing these increases the test run time
n_strucs = 4
n_mods = 1

@testset "Pipeline" begin
    # Run the whole pipeline as an integration test
    # Two structures
    runpipeline(
        i1=test_i1,
        d1=test_d1,
        i2=test_i2,
        d2=test_d2,
        out_dir=temp_dir,
        n_strucs=n_strucs,
        extra_pdbs=[test_extra_pdb_1, test_extra_pdb_2],
    )
    # Check that output files are produced
    @test length(readdir("$temp_dir/pdbs")) == n_strucs
    rm(temp_dir, recursive=true)

    # One structure
    runpipeline(
        i1=test_i1,
        d1=test_d1,
        out_dir=temp_dir,
        n_strucs=n_strucs,
        extra_pdbs=[test_extra_pdb_1, test_extra_pdb_2],
    )
    @test length(readdir("$temp_dir/pdbs")) == n_strucs
    rm(temp_dir, recursive=true)

    # Two structures with perturbation
    runpipeline(
        i1=test_i1,
        d1=test_d1,
        i2=test_i2,
        d2=test_d2,
        out_dir=temp_dir,
        n_strucs=n_strucs,
        extra_pdbs=[test_extra_pdb_1, test_extra_pdb_2],
        mod_path=test_pocket_points,
        n_mods=n_mods,
    )
    @test length(readdir("$temp_dir/pdbs_mod_1")) == n_strucs
    rm(temp_dir, recursive=true)

    # One structure with perturbation
    runpipeline(
        i1=test_i2,
        d1=test_d2,
        out_dir=temp_dir,
        n_strucs=n_strucs,
        extra_pdbs=[test_extra_pdb_1, test_extra_pdb_2],
        mod_path=test_pocket_points,
        n_mods=n_mods,
    )
    @test length(readdir("$temp_dir/pdbs_mod_1")) == n_strucs
    rm(temp_dir, recursive=true)

    @test_throws AssertionError runpipeline(i2=test_i2, d2=test_d2)
end

if run_param_test

@testset "Parameterisation" begin
    # Auto-parameterisation pipeline
    # Answer non-deterministic so cannot check it directly
    parampipeline(
        i1=test_i1,
        d1=test_d1,
        i2=test_i2,
        d2=test_d2,
        out_dir=temp_dir,
        n_strucs=n_strucs,
        tmscore_path=test_tmscore_path,
    )
    @test length(readdir("$temp_dir/tw_1_0/pdbs")) == n_strucs
    rm(temp_dir, recursive=true)

    @test_throws AssertionError parampipeline(i1=test_i1, d1=test_d1)
    @test_throws AssertionError parampipeline(i1=test_i1, d1=test_d1,
        i2=test_i2, d2=test_d2, tmscore_path="invalidpath")
end

else
    println("Parameterisation tests not run; to run these, set run_param_test ",
            "to true in runtests.jl")
end


text:
module BifurcationsBase
using ..Continuations: AbstractContinuationCache, AbstractProblemCache,
    AbstractContinuationSolver
include("timekind.jl")
include("statekind.jl")
include("contkind.jl")
include("problem.jl")
include("solution.jl")
include("solver.jl")
include("interface.jl")
include("tools.jl")
include("display.jl")
end  # module