{ chemistry, core, document, physical, ... }: let inherit(core) debug error indentation list number set string type; inherit(chemistry) compound elements ir ms nmr values; inherit(elements) calculateMassOfFormula normaliseMolecularFormula tableOfMasses; inherit(document) Heading Heading' LaTeX Paragraph Paragraph'; inherit(tableOfMasses) Cu I; default = a: b: if a != null then a else b; # null | [ Any ] | T # -> [ ] | [ Any ] | [ T ] toList = value: type.matchPrimitiveOrDefault value { null = [ ]; list = value; } [ value ]; # string | [ ? ] -> [ Document::Chunk.Paragraph ] toParagraphs = listOrString: type.matchPrimitiveOrPanic listOrString { string = [ ( Paragraph listOrString ) ]; list = list.map (paragraph: Paragraph paragraph) listOrString; }; # bool -> Synthesis -> [ Document::Chunk.Paragraph ] mapProcedure = _newline: { ... } @ synthesis: let procedure = string.trim' synthesis.procedure; result = type.matchPrimitiveOrPanic synthesis.procedure { lambda = list.map (paragraph: Paragraph' paragraph { endParagraph = "\\par"; }) ( synthesis.procedure synthesis ); string = [ ( Paragraph procedure ) ]; list = list.map (paragraph: Paragraph' paragraph { endParagraph = "\\par"; }) ( synthesis.procedure ); }; in result; # Synthesis -> Product -> [ Document::Chunk ] formatAnalysis = { ... } @ resources: { ... } @ synthesis: { analysisOnly ? false, hack ? false, ... } @ product: let substance = product.substance or synthesis.substance or { }; yield = product.yield or {}; yield' = if isLambda yield then yield product else yield; mass = yield'.mass; amount = yield'.amount or ( mass / ( substance.dalton or 1 ) ); amount' = normaliseValue ( amount ) null; equivalent = yield'.equivalent or 1; relativeEquivalents = if equivalent != 1 then " (${physical.formatValue equivalent "equivalent"})" else ""; # float relative = let relative = ( yield'.relative or ( amount / ( reactant'.amount or amount ) * 100 ) ) * 1.0 * equivalent; in if relative > 100 then debug.warn "formatAnalysis" { text = "Yield is over 100 %: ${string relative}!"; data = yield'; } relative else relative; # string | { title: string } reactant = yield'.reactant or "???"; # string title = type.matchPrimitiveOrPanic reactant { set = if reactant.title != null then reactant.title else reactant.substance.ID; string = reactant; }; # set reactant' = if set.isInstanceOf reactant then reactant else { }; # string novel = product.novel or substance.novel or false; novelText = "Diese Verbindung ist bisher literatur\\-unbekannt."; # float purity = ( yield'.purity or 1 ) * 1.0; # string purity' = if purity != 1.0 then "etwa ${physical.formatValue { value = 100*purity; precision = 1; } "percent"}-iger Reinheit und " else ""; # string mass' = if purity != 1.0 then "rein: ${physical.formatValue { value = mass * purity; precision = 2; } "gram"}, " else ""; warn = name: debug.warn "formatAnalysis" { when = product.novel or substance.novel or false; text = "${name}-data missing for novel compound ${substance.name}"; }; otherData#: T = int|float|{ from: int|float, till: int|float }|null @ { # melting: T, # boiling: T, # sublimation: T, # decomposition: T, # density: int|float|null, # }? = product.physical or ( warn "Physical" null ); eaData = substance.elements or product.elements or ( warn "EA" { } ); irData = substance.ir or product.ir or ( warn "IR" [ ] ); msData = substance.ms or product.ms or ( warn "MS" { } ); nmrData = substance.nmr or product.nmr or ( warn "NMR" { } ); reports = [] # Elementaranalyse ++ ( values.report resources otherData ) # ++ ( elements.report resources substance Elements ) ++ ( nmr.report resources nmrData ) ++ ( ir.report resources irData ) ++ ( ms.report resources msData ); failure = synthesis.failure or false || product.failure or false; in [ ( Paragraph' ( if analysisOnly then if novel then [ novelText ] else [] else if failure then if yield' == {} then [ "Es konnte kein Produkt erhalten werden." ] else [ "Es konnte kein Produkt erhalten werden und" " ${physical.formatValue { value = mass; precision = 2; } "gram"}" " (${physical.formatValue { value = amount'.value; precision = 2; } "${amount'.prefix}mol"})" " des eingesetzten ${title} wurden zurückgewonnen." ] else if yield' == {} then [ "Erhalten wurde eine unbestimmte Menge ${product.description or "Produkt"}." ] else [ "Erhalten wurden ${physical.formatValue { value = mass; precision = 2; } "gram"}" " (${mass'}${physical.formatValue { value = amount'.value*purity; precision = 2; } "${amount'.prefix}mol"})" " ${product.description or "Produkt"} mit \\mbox{${purity'}${physical.formatValue { value = relative*purity; precision = 1; } "percent"}-iger} Ausbeute" " bezogen auf eingesetztes ${title}${relativeEquivalents}.${if novel then " ${novelText}" else ""}" ] ) { endParagraph = if !hack then "\\par" else "\\par"; /*endParagraph = if !failure && any (x: x != [] && x != {} && x != null) [ eaData irData msData nmrData otherData ] then "" else "\\newline";*/ } ) ] ++ ( if failure then [] else reports ); # Synthesis -> Product -> Document::Chunk.Paragraph | null formatProductChemicals = { ... } @ resources: { ... } @ syn: { ... } @ product: if product ? "chemicals" then let chemicals = type.matchPrimitiveOrPanic product.chemicals { null = [ ]; set = set.values product.chemicals; list = product.chemicals; }; len = list.length chemicals; endParagraph = "\\par"; in if len > 1 then Paragraph' ( [ "Eingesetzt wurden:" ] ++ ( generate (x: " ${formatChemical (list.get chemicals x)},") ( len - 2 ) ) ++ [ " ${formatChemical (list.get chemicals ( len - 2 ))} und" " ${formatChemical (list.get chemicals ( len - 1 ))}." ] ) { inherit endParagraph; } else if len == 1 then Paragraph' [ "Eingesetzt wurden ${formatChemical ( list.head chemicals)}." ] { inherit endParagraph; } else null else null; # Synthesis -> [ Document::Chunk.Heading ] mapProducts = { ... } @ resources: { ... } @ synthesis: map ( { chemicals ? {}, clearPage ? false, note ? null, substance ? null, title ? null, name ? null, noHeading ? false, noMolecule ? false, postAnalysis ? [], lines ? null, ... } @ product: let product' = product // { inherit clearPage name substance title; chemicals = fixChemicals resources chemicals; }; body = ( toList ( formatProductChemicals resources synthesis product' ) ) ++ ( if note != null then if lambda.isInstanceOf note then [ (note product') ] else [ note ] else [] ) ++ ( formatAnalysis resources synthesis product' ) ++ postAnalysis; lines' = if lines != null then "[${string lines}]" else ""; body' = LaTeX ( if substance != null && !noMolecule then [ "\\renewcommand{\\NumAtom}[2]{}%" #"\\renewcommand{\\NumAtom}[2]{-[#1,,,draw=none]{\\scriptstyle#2}}%" "\\Wrapchem${lines'}{${substance { mass = true; structure = true; }}}" "{" indentation.more ] ++ body ++ [ indentation.less "}" ] else body ); body'' = if noHeading then body' else if substance != null then Heading' { bookmark = "${substance.Name}"; caption = "${substance.NameID}"; } [ body' ] { inherit clearPage; label = "substance:${substance.name or name}"; clearPageOnLastQuarter = true; } else if title != null then Heading' { bookmark = "${compound title}"; caption = "${compound title}"; } [ body' ] { inherit clearPage; label = "substance:${name}"; clearPageOnLastQuarter = true; } else body'; in body'' ); getFullTitle = { acronyms, ... }: { casus, degased, dry, molar, solvent, title }: if solvent != null then let # ToDo: Export to fluent-module specialSolvents = { "H2O" = { deu = "wässrige"; eng = "aqueous"; }; "Et2O" = { deu = "${acronyms.Et2O.as "etherische"}"; eng = "${acronyms.Et2O.as "etheral"}"; }; "EtOH" = { deu = "${acronyms.EtOH.as "ethanolische"}"; eng = "${acronyms.EtOH.as "ethanolic"}"; }; "MeOH" = { deu = "${acronyms.MeOH.as "methanolische"}"; eng = "${acronyms.MeOH.as "methanolic"}"; }; "NH3" = { deu = "ammoniakalische"; eng = null; }; "HCl" = { deu = "salz\\-saure"; eng = null; }; "HNO3" = { deu = "salpeter\\-saure"; eng = null; }; "H2SO4" = { deu = "schwefel\\-saure"; eng = null; }; }; special = specialSolvents.${solvent}.deu or null; acronym = { THF = acronyms."tetrahydrofuran"; tetrahydrofuran = acronyms."tetrahydrofuran"; }.${solvent} or null; in if isString solvent then if special != null then "${special}${default casus "r"} ${molar}${title}\\-lösung" else if acronym != null then "${molar}${title}-Lösung in ${acronym.long}" else "${molar}${title}-Lösung in ${compound solvent}" else "${molar}${title}-Lösung in ${getTitle solvent}" else if dry && degased then "trockene${default casus "m"} und entgaste${default casus "m"} ${title}" else if dry then "trockene${default casus "m"} ${title}" else if degased then "entgaste${default casus "m"} ${title}" else "${molar}${title}"; fixChemical = { acronyms, ... } @ resources: { ... } @ chemicals: name: { acronym, casus, concentration, mixture, purity, relative, substance, title, ... } @ chemical: let molar = if purity != 1.0 then "${physical.formatValue ( purity * 100 ) "percent"}-ige${default casus "r"}~" else type.matchPrimitiveOrPanic concentration { null = ""; int = "${formatConcentration concentration}~"; float = "${formatConcentration concentration}~"; list = "${concatWith ":" ( list.map formatConcentration concentration)}~"; string = { "conc" = "${acronyms.concentrated.short} "; "sat" = "${acronyms.saturated.short} "; "dil" = "${acronyms.diluted.short} "; }.${concentration} or ( debug.panic "fixChemical" "Invalid Concentration »${concentration}«" ); }; chemical' = chemical // { title = getFullTitle resources { inherit molar; title = getTitle { inherit acronym mixture substance title; }; inherit(chemical) casus dry degased solvent; }; }; relative' = chemicals.${relative} or ( debug.panic "fixChemical" "While calculating equivalent for ${name}: There is no chemical »${relative}«!" ); in if relative != null then chemical' // { equivalent = debug.warn "fixChemical" { text = "relative"; data = { chemical = { inherit(chemical) amount; }; relative = { inherit(relative') amount equivalent; }; ratio = chemical.amount / relative'.amount; }; } ( chemical.amount / relative'.amount * relative'.equivalent); } else chemical'; # ( string -> Chemical ) -> ( string -> Chemical ) fixChemicals = { ... } @ resources: { ... } @ chemicals: set.map ( name: { ... } @ chemical: type.matchPrimitiveOrPanic chemical { list = list.map (fixChemical resources chemicals name) chemical; set = fixChemical resources chemicals name chemical; } ) chemicals; # F -> G -> [ T ] -> [ ( F T ) ] filterMap = m: f: self: filter f ( list.map m self ); # [ Synthesis ] -> [ Document::Chunk.Heading ] | ! mapSyntheses = { ... } @ resources: syntheses: filterMap ( { ... } @ syn: let syn' = syn // { chemicals = fixChemicals resources syn.chemicals; }; mapSubstances = string.concatMappedWith ( { ... } @ substance: "${substance { mass = true; structure = true; }}" ) "\\arrow{0}[-90,0.3]"; product = if syn'.product or null != null then type.type.matchPrimitiveOrDefault syn'.product { lambda = syn'.product syn'; list = list.map ( product: type.callLambda product syn' ) syn'.product; } syn'.product else []; citeLiterature = { literature ? null, ... }: type.matchPrimitiveOrPanic literature { null = ""; list = "\\cite{${string.concatMappedWith ({ name, ... }: name) "," literature}}"; set = "\\cite{${literature.name}}"; }; formattedAnalysis = type.matchPrimitiveOrPanic product { null = [ ]; list = if product != [] then mapProducts resources syn' product else [ ( Paragraph' [ "Es konnte kein Produkt erhalten werden." ] { endParagraph = ""; } ) ]; set = formatAnalysis resources syn' ( product // { chemicals = fixChemicals resources product.chemicals; } ); }; lines' = if syn'.lines or null != null then "[${string syn'.lines}]" else ""; in if syn'.ignore or false then null else ( if syn'.title or null != null then Heading' ( if type.isSet syn'.title then syn'.title else { bookmark = syn'.title; caption = "${syn'.title}${citeLiterature syn'}"; } ) ( if syn'.substances or null != null then LaTeX ( [ "\\renewcommand{\\NumAtom}[2]{-[#1,,,draw=none]{\\scriptstyle#2}}%" "\\Wrapchem${lines'}{${mapSubstances syn'.substances}}" "{" indentation.more ] ++ ( mapProcedure true syn' ) ++ formattedAnalysis ++ [ indentation.less "}" ] ) else ( mapProcedure false syn' ) ++ formattedAnalysis ) { clearPageOnLastQuarter = true; } else if syn'.substance or null != null then Heading' { bookmark = "${syn'.substance.Name}"; caption = "${syn'.substance.NameID}${citeLiterature syn'}"; LaTeX = true; } ( LaTeX ( [ "\\renewcommand{\\NumAtom}[2]{-[#1,,,draw=none]{\\scriptstyle#2}}%" "\\Wrapchem${lines'}{${syn'.substance { mass = true; structure = true; }}}" "{" indentation.more ] ++ ( mapProcedure true syn' ) ++ formattedAnalysis ++ [ indentation.less "}" ] ) ) { label = "substance:${syn'.substance.name}"; clearPage = syn'.clearPage or false; clearPageOnLastQuarter = true; } else debug.panic "mapSyntheses" "Either substance or title must be set!" ) ) (x: x != null) syntheses; # Chemical -> string formatDetails = { ... } @ chemical: if list.isInstanceOf chemical then concatWith "; " ( list.map formatDetails' chemical ) else formatDetails' chemical; # Chemical -> string formatDetails' = { ... } @ chemical: let details = ( if chemical.substance != null then [ chemical.substance.ID ] else [ ] ) ++ ( if chemical.volume != null then let precision = if chemical.kind != "solvent" then null else if chemical.volume < 20 then 1 else 0; volume = normaliseValue ( chemical.volume / 1000.0 ) null; volume' = physical.formatValue { value = volume.value; inherit precision; } "${volume.prefix}litre"; fullVolume = normaliseValue ( chemical.times * chemical.volume / 1000.0 ) null; fullVolume' = physical.formatValue { value = fullVolume.value; inherit precision; } "${fullVolume.prefix}litre"; in if chemical.times != null then [ "${string chemical.times}×${volume'}=${fullVolume'}" ] else [ volume' ] else [ ] ) ++ ( if chemical.mass != null then let mass = normaliseValue ( chemical.mass ) null; volume = normaliseValue ( chemical.solvent.volume / 1000.0 ) (-3); precision = if chemical.solvent.volume < 20 then 1 else 0; solvent = if chemical.solvent != null && chemical.solvent ? volume then " in ${physical.formatValue { value = volume.value; inherit precision; } "${volume.prefix}litre"}" else ""; in [ "${( physical.formatValue { value = mass.value; precision = 2; } "${mass.prefix}gram" )}" ] else [ ] ) ++ ( if chemical.amount != null then let amount = normaliseValue ( chemical.amount ) ( chemical.factor or null ); in [ ( physical.formatValue { value = amount.value; precision = 2; } "${amount.prefix}mol" ) ] else [ ] ) ++ ( if chemical.equivalent != null then debug.info "formatDetails'" { text = "Equivalent"; data = chemical.equivalent; } ( if chemical.kind == "catalyst" then [ ( physical.formatValue { value = chemical.equivalent; precision = null; } "equivalent" ) ] else [ ( physical.formatValue { value = chemical.equivalent; precision = 1; } "equivalent" ) ] ) else [ ] ) ++ ( if chemical.details != null then [ chemical.details ] else [ ] ); in string.concatWith ", " details; getUnitPrefix#: integer -> string | ! = factor: if factor == -24 then "yokto" else if factor == -21 then "zepto" else if factor == -18 then "atto" else if factor == -15 then "femto" else if factor == -12 then "pico" else if factor == -9 then "nano" else if factor == -6 then "micro" else if factor == -3 then "milli" else if factor == 0 then "" else if factor == 3 then "kilo" else if factor == 6 then "mega" else if factor == 9 then "giga" else if factor == 12 then "tera" else if factor == 15 then "peta" else if factor == 18 then "exa" else if factor == 21 then "zetta" else if factor == 24 then "yotta" else error.throw "getUnitPrefix: Invalid Factor: ${string factor}"; # Number -> null | Number -> { value: Number; prefix = string; } normaliseValue = input: factor: let absInput = number.abs input; factor' = if factor != null then factor else if absInput >= 1000000000000000.0 then 15 else if absInput >= 1000000000000.0 then 12 else if absInput >= 1000000000.0 then 9 else if absInput >= 1000000.0 then 6 else if absInput >= 1000.0 then 3 else if absInput >= 1.0 then 0 else if absInput >= 0.001 then -3 else if absInput >= 0.000001 then -6 else if absInput >= 0.000000001 then -9 else if absInput >= 0.000000000001 then -12 else if absInput >= 0.000000000000001 then -15 else 0; prefix = getUnitPrefix factor'; value = input * ( number.pow 10 ( 0 - factor' ) ); in { inherit value prefix; }; # { acronym, mixture, substance, title, ... } -> string getTitle = { acronym ? null, mixture ? null, substance ? null, title ? null, ... }: if title != null then compound title else if acronym != null then acronym.long else if substance != null then substance.Name else if mixture != null then string.concatMappedWith getTitle "-" mixture else "???"; # Chemical -> string formatName = { ... } @ chemical: if list.isInstanceOf chemical then "${string.concatMappedWith ({ title, ... }: title) "-" chemical}-Mischung" else if chemical.the or null != null then "${chemical.the} ${chemical.title}" else chemical.title; # Chemical -> string formatChemical = { ... } @ chemical: let name = formatName chemical; details = formatDetails chemical; in if details != "" then "${name} (${details})" else name; # Number -> string | ! formatConcentration = concentration: let concentration' = normaliseValue concentration null; in if concentration != null then "${( physical.formatValue { value = concentration'.value; precision = 1; } "${concentration'.prefix}molar" )}" else debug.panic "formatConcentration" "Concentration is null!"; Chemical = kind: { acronym ? null, amount ? null, concentration ? null, dalton ? null, degased ? false, density ? null, details ? null, dry ? false, equivalent ? null, factor ? null, formula ? null, mass ? null, mixture ? null, purity ? 1.0, relative ? null, simple ? null, solvent ? null, substance ? null, the ? null, times ? null, title ? null, volume ? null } @ chemical: casus: let formula' = normaliseMolecularFormula ( default formula ( substance.formula or [ ] ) ); dalton' = calculateMassOfFormula formula'; chemical' = { __type__ = "Chemical"; concentration = if concentration != null then concentration else if mixture != null then list.map ({ concentration ? null, ... }: concentration) mixture else null; dalton = default dalton ( substance.dalton or dalton' ); density = default density ( substance.density or null ); formula = formula'; inherit acronym amount casus degased details dry equivalent factor kind mass mixture purity relative simple solvent substance the times title volume; }; chemical'' = chemical' // { amount = debug.info "chemical" { data = chemical; } ( if chemical'.amount != null then chemical'.amount else if chemical'.volume != null then if chemical'.mass != null then debug.panic "Chemical" "Volume and mass are mutualy exclusive!" else if chemical'.concentration != null then if isFloat chemical'.concentration then chemical'.volume * chemical'.concentration / 1000 else null else if chemical'.density != null && chemical'.dalton != null then chemical'.purity * chemical'.volume * chemical'.density / chemical'.dalton else null else if chemical'.mass != null then if chemical'.dalton != null then chemical'.mass / chemical'.dalton else null else null ); }; in if kind == "reagent" || kind == "catalyst" then if chemical''.amount == null then debug.panic "Chemical" { text = "Catalysts and Reagents need to have amount of substance (`amount`) given. Perhaps you want Material."; data = chemical''; } else if chemical''.equivalent == null && chemical''.relative == null then debug.panic "Chemical" "Catalysts and Reagents need to have `equivalent` given. Perhaps you want Material." else chemical'' else chemical''; Catalyst' = { ... } @ attrs: Chemical "catalyst" attrs; Material' = { ... } @ attrs: Chemical "material" attrs; Product' = { ... } @ attrs: Chemical "product" attrs; Reagent' = { ... } @ attrs: Chemical "reagent" attrs; Solvent' = { ... } @ attrs: Chemical "solvent" attrs; Catalyst = { ... } @ attrs: Catalyst' attrs null; Material = { ... } @ attrs: Material' attrs null; Product = { ... } @ attrs: Product' attrs null; Reagent = { ... } @ attrs: Reagent' attrs null; Solvent = { ... } @ attrs: Solvent' attrs null; in { inherit mapSyntheses; inherit formatChemical formatDetails formatName; inherit Catalyst Material Product Reagent Solvent; inherit Catalyst' Material' Product' Reagent' Solvent'; }