2024-04-11 15:51:56 +02:00
// createGeneticAnalysis provides functions to create a genetic analysis
// These are performed on one or more genome files.
// They produce 3 kinds of results: Monogenic Diseases, Polygenic Diseases and Traits
// They can be performed on a Person or a Couple
// Couple analyses provide an analysis of the prospective offspring of the couple
package createGeneticAnalysis
// TODO: Some of the probabilities produced by this package are wrong
2024-06-02 10:43:39 +02:00
// In this package, we are assuming that genetic recombination (the formation of the genetic sequences for the sperm/eggs)
// happens randomly for each allele locus
2024-04-11 15:51:56 +02:00
// In reality, the recombination break points occur less often, and larger portions of each chromosome remain intact.
// This effects the estimates and probabilities for all of the generated analyses
// In particular, the probability of passing a defective gene does not increase as much as this package currently
2024-06-02 10:43:39 +02:00
// estimates that it does, in the case of multiple defects existing in the same monongenic-disease-causing gene.
2024-04-11 15:51:56 +02:00
// Also, based on my research, I believe that recombination break points are less likely to occur within genes, meaning they are more likely to occur at the gene boundaries (codons)
// We need to remedy this problem and fix this package
// Research gene linkage and recombination to understand more.
//
2024-06-02 10:43:39 +02:00
// The phase of a loci is actually relevant and important for determining the person-has-disease status and will-pass-a-variant probability
// Users who have multiple heterozygous single-base mutations on different locations of the same gene may have the disease,
// but we need their genome locations to be phased to be able to know
// Having multiple variants within a gene might not increase the probability of passing a variant,
// assuming all of those variants were on the same chromosome
2024-04-11 15:51:56 +02:00
// Thus, we need phased loci to determine an accurate will-pass-a-variant probability
2024-06-02 10:43:39 +02:00
// We will still be able to determine will-pass-a-variant probabilities for users who only have 1 mutation on 1 base in the entire gene,
2024-04-11 15:51:56 +02:00
// regardless of if their loci phase is known or not. That probability is 50%.
2024-06-02 10:43:39 +02:00
// TODO: We want to eventually use neural nets for both trait and polygenic disease analysis (see geneticPrediction.go)
2024-04-11 15:51:56 +02:00
// These will be trained on a set of genomes and will output a probability analysis for each trait/disease
// This is only possible once we get access to the necessary training data
//
// This is how offspring trait prediction could work with the neural net model:
// Both users will share all relevant SNPS base pairs that determine the trait on their profile.
// Each location has 4 possible outcomes, so for 1000 SNPs, there are 4^1000 possible offspring outcomes for a given couple. (this
// is actually too high because recombination break points do not occur at each locus, see genetic linkage)
// This is too many options for us to check all of them.
// Seekia will create 100 offspring that would be produced from both users, and run each offspring through the neural net.
// Each offspring would be different. The allele from each parent for each SNP would be randomly chosen.
// The user can choose how many prospective offspring to create in the settings.
// More offspring will take longer, but will yield a more accurate trait probability.
// Seekia will show the the average trait result and a chart showing the trait results for all created offspring.
// TODO: Add the ability to weight different genome files based on their reliability.
// Some files are much more accurate because they record each location many times.
import "seekia/resources/geneticReferences/locusMetadata"
import "seekia/resources/geneticReferences/monogenicDiseases"
import "seekia/resources/geneticReferences/polygenicDiseases"
import "seekia/resources/geneticReferences/traits"
2024-06-02 10:43:39 +02:00
import "seekia/internal/encoding"
import "seekia/internal/genetics/geneticAnalysis"
2024-04-11 15:51:56 +02:00
import "seekia/internal/genetics/locusValue"
import "seekia/internal/genetics/prepareRawGenomes"
import "seekia/internal/helpers"
import "errors"
import "math"
import "strings"
import "slices"
2024-06-02 10:43:39 +02:00
import "maps"
2024-04-11 15:51:56 +02:00
func verifyBasePair ( inputBasePair string ) bool {
base1 , base2 , delimiterFound := strings . Cut ( inputBasePair , ";" )
if ( delimiterFound == false ) {
return false
}
// I = Insertion
// D = Deletion
validBasesList := [ ] string { "C" , "A" , "T" , "G" , "I" , "D" }
baseIsValid := slices . Contains ( validBasesList , base1 )
if ( baseIsValid == false ) {
return false
}
baseIsValid = slices . Contains ( validBasesList , base2 )
if ( baseIsValid == false ) {
return false
}
return true
}
//Outputs:
// -bool: Process completed (it was not stopped manually before completion)
2024-06-02 10:43:39 +02:00
// -string: New Genetic analysis string (Encoded in MessagePack)
2024-04-11 15:51:56 +02:00
// -error
func CreatePersonGeneticAnalysis ( genomesList [ ] prepareRawGenomes . RawGenomeWithMetadata , updatePercentageCompleteFunction func ( int ) error , checkIfProcessIsStopped func ( ) bool ) ( bool , string , error ) {
prepareRawGenomesUpdatePercentageCompleteFunction := func ( newPercentage int ) error {
newPercentageCompletion , err := helpers . ScaleNumberProportionally ( true , newPercentage , 0 , 100 , 0 , 50 )
if ( err != nil ) { return err }
err = updatePercentageCompleteFunction ( newPercentageCompletion )
if ( err != nil ) { return err }
return nil
}
genomesWithMetadataList , allRawGenomeIdentifiersList , multipleGenomesExist , onlyExcludeConflictsGenomeIdentifier , onlyIncludeSharedGenomeIdentifier , err := prepareRawGenomes . GetGenomesWithMetadataListFromRawGenomesList ( genomesList , prepareRawGenomesUpdatePercentageCompleteFunction )
if ( err != nil ) { return false , "" , err }
2024-06-02 10:43:39 +02:00
newGeneticAnalysisObject := geneticAnalysis . PersonAnalysis {
AnalysisVersion : 1 ,
CombinedGenomesExist : multipleGenomesExist ,
AllRawGenomeIdentifiersList : allRawGenomeIdentifiersList ,
2024-04-11 15:51:56 +02:00
}
if ( multipleGenomesExist == true ) {
2024-06-02 10:43:39 +02:00
newGeneticAnalysisObject . OnlyExcludeConflictsGenomeIdentifier = onlyExcludeConflictsGenomeIdentifier
newGeneticAnalysisObject . OnlyIncludeSharedGenomeIdentifier = onlyIncludeSharedGenomeIdentifier
2024-04-11 15:51:56 +02:00
}
processIsStopped := checkIfProcessIsStopped ( )
if ( processIsStopped == true ) {
return false , "" , nil
}
monogenicDiseasesList , err := monogenicDiseases . GetMonogenicDiseaseObjectsList ( )
if ( err != nil ) { return false , "" , err }
2024-06-02 10:43:39 +02:00
// Map Structure: Disease Name -> PersonMonogenicDiseaseInfo
analysisMonogenicDiseasesMap := make ( map [ string ] geneticAnalysis . PersonMonogenicDiseaseInfo )
2024-04-11 15:51:56 +02:00
for _ , monogenicDiseaseObject := range monogenicDiseasesList {
diseaseName := monogenicDiseaseObject . DiseaseName
2024-06-02 10:43:39 +02:00
personDiseaseAnalysisObject , err := getPersonMonogenicDiseaseAnalysis ( genomesWithMetadataList , monogenicDiseaseObject )
2024-04-11 15:51:56 +02:00
if ( err != nil ) { return false , "" , err }
2024-06-02 10:43:39 +02:00
analysisMonogenicDiseasesMap [ diseaseName ] = personDiseaseAnalysisObject
2024-04-11 15:51:56 +02:00
}
2024-06-02 10:43:39 +02:00
newGeneticAnalysisObject . MonogenicDiseasesMap = analysisMonogenicDiseasesMap
2024-04-11 15:51:56 +02:00
polygenicDiseaseObjectsList , err := polygenicDiseases . GetPolygenicDiseaseObjectsList ( )
if ( err != nil ) { return false , "" , err }
2024-06-02 10:43:39 +02:00
// Map Structure: Disease Name -> PersonPolygenicDiseaseInfo
analysisPolygenicDiseasesMap := make ( map [ string ] geneticAnalysis . PersonPolygenicDiseaseInfo )
2024-04-11 15:51:56 +02:00
for _ , diseaseObject := range polygenicDiseaseObjectsList {
2024-06-02 10:43:39 +02:00
personDiseaseAnalysisObject , err := getPersonPolygenicDiseaseAnalysis ( genomesWithMetadataList , diseaseObject )
2024-04-11 15:51:56 +02:00
if ( err != nil ) { return false , "" , err }
diseaseName := diseaseObject . DiseaseName
2024-06-02 10:43:39 +02:00
analysisPolygenicDiseasesMap [ diseaseName ] = personDiseaseAnalysisObject
2024-04-11 15:51:56 +02:00
}
2024-06-02 10:43:39 +02:00
newGeneticAnalysisObject . PolygenicDiseasesMap = analysisPolygenicDiseasesMap
2024-04-11 15:51:56 +02:00
traitObjectsList , err := traits . GetTraitObjectsList ( )
if ( err != nil ) { return false , "" , err }
2024-06-02 10:43:39 +02:00
// Map Structure: Trait Name -> PersonTraitInfo
analysisTraitsMap := make ( map [ string ] geneticAnalysis . PersonTraitInfo )
2024-04-11 15:51:56 +02:00
for _ , traitObject := range traitObjectsList {
2024-06-02 10:43:39 +02:00
personTraitAnalysisObject , err := getPersonTraitAnalysis ( genomesWithMetadataList , traitObject )
2024-04-11 15:51:56 +02:00
if ( err != nil ) { return false , "" , err }
traitName := traitObject . TraitName
2024-06-02 10:43:39 +02:00
analysisTraitsMap [ traitName ] = personTraitAnalysisObject
2024-04-11 15:51:56 +02:00
}
2024-06-02 10:43:39 +02:00
newGeneticAnalysisObject . TraitsMap = analysisTraitsMap
analysisBytes , err := encoding . EncodeMessagePackBytes ( newGeneticAnalysisObject )
2024-04-11 15:51:56 +02:00
if ( err != nil ) { return false , "" , err }
analysisString := string ( analysisBytes )
return true , analysisString , nil
}
//Outputs:
// -bool: Process completed (was not manually stopped mid-way)
2024-06-02 10:43:39 +02:00
// -string: Couple genetic analysis string (encoded in MessagePack)
2024-04-11 15:51:56 +02:00
// -error
2024-06-02 10:43:39 +02:00
func CreateCoupleGeneticAnalysis ( person1GenomesList [ ] prepareRawGenomes . RawGenomeWithMetadata , person2GenomesList [ ] prepareRawGenomes . RawGenomeWithMetadata , updatePercentageCompleteFunction func ( int ) error , checkIfProcessIsStopped func ( ) bool ) ( bool , string , error ) {
2024-04-11 15:51:56 +02:00
2024-06-02 10:43:39 +02:00
person1PrepareRawGenomesUpdatePercentageCompleteFunction := func ( newPercentage int ) error {
2024-04-11 15:51:56 +02:00
newPercentageCompletion , err := helpers . ScaleNumberProportionally ( true , newPercentage , 0 , 100 , 0 , 25 )
if ( err != nil ) { return err }
err = updatePercentageCompleteFunction ( newPercentageCompletion )
if ( err != nil ) { return err }
return nil
}
2024-06-02 10:43:39 +02:00
person1GenomesWithMetadataList , allPerson1RawGenomeIdentifiersList , person1HasMultipleGenomes , person1OnlyExcludeConflictsGenomeIdentifier , person1OnlyIncludeSharedGenomeIdentifier , err := prepareRawGenomes . GetGenomesWithMetadataListFromRawGenomesList ( person1GenomesList , person1PrepareRawGenomesUpdatePercentageCompleteFunction )
2024-04-11 15:51:56 +02:00
if ( err != nil ) { return false , "" , err }
processIsStopped := checkIfProcessIsStopped ( )
if ( processIsStopped == true ) {
return false , "" , nil
}
2024-06-02 10:43:39 +02:00
person2PrepareRawGenomesUpdatePercentageCompleteFunction := func ( newPercentage int ) error {
2024-04-11 15:51:56 +02:00
newPercentageCompletion , err := helpers . ScaleNumberProportionally ( true , newPercentage , 0 , 100 , 25 , 50 )
if ( err != nil ) { return err }
err = updatePercentageCompleteFunction ( newPercentageCompletion )
if ( err != nil ) { return err }
return nil
}
2024-06-02 10:43:39 +02:00
person2GenomesWithMetadataList , allPerson2RawGenomeIdentifiersList , person2HasMultipleGenomes , person2OnlyExcludeConflictsGenomeIdentifier , person2OnlyIncludeSharedGenomeIdentifier , err := prepareRawGenomes . GetGenomesWithMetadataListFromRawGenomesList ( person2GenomesList , person2PrepareRawGenomesUpdatePercentageCompleteFunction )
2024-04-11 15:51:56 +02:00
if ( err != nil ) { return false , "" , err }
processIsStopped = checkIfProcessIsStopped ( )
if ( processIsStopped == true ) {
return false , "" , nil
}
// The analysis will analyze either 1 or 2 genome pairs
// The gui will display the results from each pair
//Outputs:
2024-06-02 10:43:39 +02:00
// -[16]byte: Pair 1 Person 1 Genome Identifier
// -[16]byte: Pair 1 Person 2 Genome Identifier
2024-04-11 15:51:56 +02:00
// -bool: Second pair exists
2024-06-02 10:43:39 +02:00
// -[16]byte: Pair 2 Person 1 Genome Identifier
// -[16]byte: Pair 2 Person 2 Genome Identifier
2024-04-11 15:51:56 +02:00
// -error
2024-06-02 10:43:39 +02:00
getGenomePairsToAnalyze := func ( ) ( [ 16 ] byte , [ 16 ] byte , bool , [ 16 ] byte , [ 16 ] byte , error ) {
2024-04-11 15:51:56 +02:00
2024-06-02 10:43:39 +02:00
if ( person1HasMultipleGenomes == true && person2HasMultipleGenomes == true ) {
2024-04-11 15:51:56 +02:00
2024-06-02 10:43:39 +02:00
return person1OnlyExcludeConflictsGenomeIdentifier , person2OnlyExcludeConflictsGenomeIdentifier , true , person1OnlyIncludeSharedGenomeIdentifier , person2OnlyIncludeSharedGenomeIdentifier , nil
2024-04-11 15:51:56 +02:00
}
2024-06-02 10:43:39 +02:00
if ( person1HasMultipleGenomes == true && person2HasMultipleGenomes == false ) {
2024-04-11 15:51:56 +02:00
2024-06-02 10:43:39 +02:00
person2GenomeIdentifier := allPerson2RawGenomeIdentifiersList [ 0 ]
2024-04-11 15:51:56 +02:00
2024-06-02 10:43:39 +02:00
return person1OnlyExcludeConflictsGenomeIdentifier , person2GenomeIdentifier , true , person1OnlyIncludeSharedGenomeIdentifier , person2GenomeIdentifier , nil
2024-04-11 15:51:56 +02:00
}
2024-06-02 10:43:39 +02:00
if ( person1HasMultipleGenomes == false && person2HasMultipleGenomes == true ) {
2024-04-11 15:51:56 +02:00
2024-06-02 10:43:39 +02:00
person1GenomeIdentifier := allPerson1RawGenomeIdentifiersList [ 0 ]
2024-04-11 15:51:56 +02:00
2024-06-02 10:43:39 +02:00
return person1GenomeIdentifier , person2OnlyExcludeConflictsGenomeIdentifier , true , person1GenomeIdentifier , person2OnlyIncludeSharedGenomeIdentifier , nil
2024-04-11 15:51:56 +02:00
}
2024-06-02 10:43:39 +02:00
//person1HasMultipleGenomes == false && person2HasMultipleGenomes == false
2024-04-11 15:51:56 +02:00
2024-06-02 10:43:39 +02:00
person1GenomeIdentifier := allPerson1RawGenomeIdentifiersList [ 0 ]
person2GenomeIdentifier := allPerson2RawGenomeIdentifiersList [ 0 ]
2024-04-11 15:51:56 +02:00
2024-06-02 10:43:39 +02:00
return person1GenomeIdentifier , person2GenomeIdentifier , false , [ 16 ] byte { } , [ 16 ] byte { } , nil
2024-04-11 15:51:56 +02:00
}
2024-06-02 10:43:39 +02:00
pair1Person1GenomeIdentifier , pair1Person2GenomeIdentifier , genomePair2Exists , pair2Person1GenomeIdentifier , pair2Person2GenomeIdentifier , err := getGenomePairsToAnalyze ( )
2024-04-11 15:51:56 +02:00
if ( err != nil ) { return false , "" , err }
2024-06-02 10:43:39 +02:00
newCoupleAnalysis := geneticAnalysis . CoupleAnalysis {
AnalysisVersion : 1 ,
Pair1Person1GenomeIdentifier : pair1Person1GenomeIdentifier ,
Pair1Person2GenomeIdentifier : pair1Person2GenomeIdentifier ,
SecondPairExists : genomePair2Exists ,
Person1HasMultipleGenomes : person1HasMultipleGenomes ,
Person2HasMultipleGenomes : person2HasMultipleGenomes ,
2024-04-11 15:51:56 +02:00
}
if ( genomePair2Exists == true ) {
// At least 1 of the people have multiple genomes
2024-06-02 10:43:39 +02:00
newCoupleAnalysis . Pair2Person1GenomeIdentifier = pair2Person1GenomeIdentifier
newCoupleAnalysis . Pair2Person2GenomeIdentifier = pair2Person2GenomeIdentifier
2024-04-11 15:51:56 +02:00
2024-06-02 10:43:39 +02:00
if ( person1HasMultipleGenomes == true ) {
newCoupleAnalysis . Person1OnlyExcludeConflictsGenomeIdentifier = person1OnlyExcludeConflictsGenomeIdentifier
newCoupleAnalysis . Person1OnlyIncludeSharedGenomeIdentifier = person1OnlyIncludeSharedGenomeIdentifier
2024-04-11 15:51:56 +02:00
}
2024-06-02 10:43:39 +02:00
if ( person2HasMultipleGenomes == true ) {
newCoupleAnalysis . Person2OnlyExcludeConflictsGenomeIdentifier = person2OnlyExcludeConflictsGenomeIdentifier
newCoupleAnalysis . Person2OnlyIncludeSharedGenomeIdentifier = person2OnlyIncludeSharedGenomeIdentifier
2024-04-11 15:51:56 +02:00
}
}
2024-06-02 10:43:39 +02:00
// We compute and add monogenic disease probabilities
2024-04-11 15:51:56 +02:00
monogenicDiseasesList , err := monogenicDiseases . GetMonogenicDiseaseObjectsList ( )
if ( err != nil ) { return false , "" , err }
2024-06-02 10:43:39 +02:00
// Map Structure: Disease Name -> OffspringMonogenicDiseaseInfo
offspringMonogenicDiseasesMap := make ( map [ string ] geneticAnalysis . OffspringMonogenicDiseaseInfo )
2024-04-11 15:51:56 +02:00
for _ , diseaseObject := range monogenicDiseasesList {
diseaseName := diseaseObject . DiseaseName
variantsList := diseaseObject . VariantsList
diseaseIsDominantOrRecessive := diseaseObject . DominantOrRecessive
2024-06-02 10:43:39 +02:00
person1DiseaseAnalysisObject , err := getPersonMonogenicDiseaseAnalysis ( person1GenomesWithMetadataList , diseaseObject )
2024-04-11 15:51:56 +02:00
if ( err != nil ) { return false , "" , err }
2024-06-02 10:43:39 +02:00
person2DiseaseAnalysisObject , err := getPersonMonogenicDiseaseAnalysis ( person2GenomesWithMetadataList , diseaseObject )
2024-04-11 15:51:56 +02:00
if ( err != nil ) { return false , "" , err }
2024-06-02 10:43:39 +02:00
// This map stores the number of variants tested in each person's genome
// Map Structure: Genome Identifier -> Number of variants tested
numberOfVariantsTestedMap := make ( map [ [ 16 ] byte ] int )
// This map stores the offspring disease probabilities for each genome pair.
// A genome pair is a concatenation of two genome identifiers
// If a map entry doesn't exist, the probabilities are unknown for that genome pair
// Map Structure: Genome Pair Identifier -> OffspringMonogenicDiseaseProbabilities
offspringMonogenicDiseaseInfoMap := make ( map [ [ 32 ] byte ] geneticAnalysis . OffspringGenomePairMonogenicDiseaseInfo )
2024-04-11 15:51:56 +02:00
// This will calculate the probability of monogenic disease for the offspring from the two specified genomes
2024-06-02 10:43:39 +02:00
// It also calculates the probabilities for each monogenic disease variant for the offspring
// It then adds the genome pair disease information to the offspringMonogenicDiseaseInfoMap and numberOfVariantsTestedMap
addGenomePairInfoToDiseaseMaps := func ( person1GenomeIdentifier [ 16 ] byte , person2GenomeIdentifier [ 16 ] byte ) error {
2024-04-11 15:51:56 +02:00
//Outputs:
// -bool: Probability is known
// -int: Probability of passing a disease variant (value between 0 and 100)
// -int: Number of variants tested
2024-06-02 10:43:39 +02:00
getPersonWillPassDiseaseVariantProbability := func ( personDiseaseAnalysisObject geneticAnalysis . PersonMonogenicDiseaseInfo , genomeIdentifier [ 16 ] byte ) ( bool , int , int ) {
2024-04-11 15:51:56 +02:00
2024-06-02 10:43:39 +02:00
personDiseaseInfoMap := personDiseaseAnalysisObject . MonogenicDiseaseInfoMap
2024-04-11 15:51:56 +02:00
2024-06-02 10:43:39 +02:00
personGenomeDiseaseInfoObject , exists := personDiseaseInfoMap [ genomeIdentifier ]
2024-04-11 15:51:56 +02:00
if ( exists == false ) {
2024-06-02 10:43:39 +02:00
return false , 0 , 0
2024-04-11 15:51:56 +02:00
}
2024-06-02 10:43:39 +02:00
personGenomeProbabilityOfPassingADiseaseVariant := personGenomeDiseaseInfoObject . ProbabilityOfPassingADiseaseVariant
2024-04-11 15:51:56 +02:00
2024-06-02 10:43:39 +02:00
personGenomeNumberOfVariantsTested := personGenomeDiseaseInfoObject . NumberOfVariantsTested
2024-04-11 15:51:56 +02:00
2024-06-02 10:43:39 +02:00
return true , personGenomeProbabilityOfPassingADiseaseVariant , personGenomeNumberOfVariantsTested
2024-04-11 15:51:56 +02:00
}
2024-06-02 10:43:39 +02:00
person1ProbabilityIsKnown , person1WillPassVariantProbability , person1NumberOfVariantsTested := getPersonWillPassDiseaseVariantProbability ( person1DiseaseAnalysisObject , person1GenomeIdentifier )
2024-04-11 15:51:56 +02:00
2024-06-02 10:43:39 +02:00
person2ProbabilityIsKnown , person2WillPassVariantProbability , person2NumberOfVariantsTested := getPersonWillPassDiseaseVariantProbability ( person2DiseaseAnalysisObject , person2GenomeIdentifier )
2024-04-11 15:51:56 +02:00
2024-06-02 10:43:39 +02:00
offspringHasDiseaseProbabilityIsKnown , percentageProbabilityOffspringHasDisease , offspringHasAVariantProbabilityIsKnown , percentageProbabilityOffspringHasVariant , err := GetOffspringMonogenicDiseaseProbabilities ( diseaseIsDominantOrRecessive , person1ProbabilityIsKnown , person1WillPassVariantProbability , person2ProbabilityIsKnown , person2WillPassVariantProbability )
if ( err != nil ) { return err }
2024-04-11 15:51:56 +02:00
2024-06-02 10:43:39 +02:00
// Now we calculate the probabilities for individual variants
2024-04-11 15:51:56 +02:00
2024-06-02 10:43:39 +02:00
offspringVariantsInfoMap := make ( map [ [ 3 ] byte ] geneticAnalysis . OffspringMonogenicDiseaseVariantInfo )
2024-04-11 15:51:56 +02:00
2024-06-02 10:43:39 +02:00
for _ , variantObject := range variantsList {
2024-04-11 15:51:56 +02:00
2024-06-02 10:43:39 +02:00
variantIdentifierHex := variantObject . VariantIdentifier
2024-04-11 15:51:56 +02:00
2024-06-02 10:43:39 +02:00
variantIdentifier , err := encoding . DecodeHexStringTo3ByteArray ( variantIdentifierHex )
if ( err != nil ) { return err }
2024-04-11 15:51:56 +02:00
2024-06-02 10:43:39 +02:00
// Outputs:
// -bool: Probabilities are known
// -int: Lower bound Percentage Probability that offspring will have 0 mutations
// -int: Upper bound Percentage Probability that offspring will have 0 mutations
// -int: Lower bound Percentage Probability that offspring will have 1 mutation
// -int: Upper bound Percentage Probability that offspring will have 1 mutation
// -int: Lower bound Percentage Probability that offspring will have 2 mutations
// -int: Upper bound Percentage Probability that offspring will have 2 mutations
2024-04-11 15:51:56 +02:00
// -error
2024-06-02 10:43:39 +02:00
getOffspringVariantProbabilities := func ( ) ( bool , int , int , int , int , int , int , error ) {
2024-04-11 15:51:56 +02:00
2024-06-02 10:43:39 +02:00
//Outputs:
// -bool: Probability is known
// -float64: Probability that person will pass variant to offspring (between 0 and 1)
// -error
getPersonWillPassVariantProbability := func ( personDiseaseAnalysisObject geneticAnalysis . PersonMonogenicDiseaseInfo , personGenomeIdentifier [ 16 ] byte ) ( bool , float64 , error ) {
2024-04-11 15:51:56 +02:00
2024-06-02 10:43:39 +02:00
personDiseaseInfoMap := personDiseaseAnalysisObject . MonogenicDiseaseInfoMap
2024-04-11 15:51:56 +02:00
2024-06-02 10:43:39 +02:00
personGenomeDiseaseInfoObject , exists := personDiseaseInfoMap [ personGenomeIdentifier ]
2024-04-11 15:51:56 +02:00
if ( exists == false ) {
2024-06-02 10:43:39 +02:00
return false , 0 , nil
2024-04-11 15:51:56 +02:00
}
2024-06-02 10:43:39 +02:00
personGenomeDiseaseVariantsMap := personGenomeDiseaseInfoObject . VariantsInfoMap
personVariantInfoObject , exists := personGenomeDiseaseVariantsMap [ variantIdentifier ]
2024-04-11 15:51:56 +02:00
if ( exists == false ) {
2024-06-02 10:43:39 +02:00
// The genome does not have information about this variant
2024-04-11 15:51:56 +02:00
return false , 0 , nil
}
2024-06-02 10:43:39 +02:00
base1HasVariant := personVariantInfoObject . Base1HasVariant
base2HasVariant := personVariantInfoObject . Base2HasVariant
if ( base1HasVariant == true && base2HasVariant == true ) {
2024-04-11 15:51:56 +02:00
return true , 1 , nil
}
2024-06-02 10:43:39 +02:00
if ( base1HasVariant == true && base2HasVariant == false ) {
2024-04-11 15:51:56 +02:00
return true , 0.5 , nil
}
2024-06-02 10:43:39 +02:00
if ( base1HasVariant == false && base2HasVariant == true ) {
return true , 0.5 , nil
2024-04-11 15:51:56 +02:00
}
2024-06-02 10:43:39 +02:00
// Neither base has a variant
return true , 0 , nil
2024-04-11 15:51:56 +02:00
}
2024-06-02 10:43:39 +02:00
person1VariantProbabilityIsKnown , person1WillPassVariantProbability , err := getPersonWillPassVariantProbability ( person1DiseaseAnalysisObject , person1GenomeIdentifier )
if ( err != nil ) { return false , 0 , 0 , 0 , 0 , 0 , 0 , err }
2024-04-11 15:51:56 +02:00
2024-06-02 10:43:39 +02:00
person2VariantProbabilityIsKnown , person2WillPassVariantProbability , err := getPersonWillPassVariantProbability ( person2DiseaseAnalysisObject , person2GenomeIdentifier )
if ( err != nil ) { return false , 0 , 0 , 0 , 0 , 0 , 0 , err }
if ( person1VariantProbabilityIsKnown == false && person2VariantProbabilityIsKnown == false ) {
return false , 0 , 0 , 0 , 0 , 0 , 0 , nil
}
2024-04-11 15:51:56 +02:00
//Outputs:
// -int: Percentage Probability of 0 mutations
// -int: Percentage Probability of 1 mutation
// -int: Percentage Probability of 2 mutations
// -error
2024-06-02 10:43:39 +02:00
getOffspringVariantProbabilities := func ( person1WillPassVariantProbability float64 , person2WillPassVariantProbability float64 ) ( int , int , int , error ) {
// This is the probability that neither person will pass the variant
2024-04-11 15:51:56 +02:00
// P = P(!A) * P(!B)
2024-06-02 10:43:39 +02:00
probabilityOf0Mutations := ( 1 - person1WillPassVariantProbability ) * ( 1 - person2WillPassVariantProbability )
2024-04-11 15:51:56 +02:00
2024-06-02 10:43:39 +02:00
// This is the probability that either person will pass the variant, but not both
2024-04-11 15:51:56 +02:00
// P(A XOR B) = P(A) + P(B) - (2 * P(A and B))
2024-06-02 10:43:39 +02:00
probabilityOf1Mutation := person1WillPassVariantProbability + person2WillPassVariantProbability - ( 2 * person1WillPassVariantProbability * person2WillPassVariantProbability )
2024-04-11 15:51:56 +02:00
2024-06-02 10:43:39 +02:00
// This is the probability that both people will pass the variant
2024-04-11 15:51:56 +02:00
// P(A and B) = P(A) * P(B)
2024-06-02 10:43:39 +02:00
probabilityOf2Mutations := person1WillPassVariantProbability * person2WillPassVariantProbability
2024-04-11 15:51:56 +02:00
percentageProbabilityOf0Mutations , err := helpers . FloorFloat64ToInt ( probabilityOf0Mutations * 100 )
if ( err != nil ) { return 0 , 0 , 0 , err }
percentageProbabilityOf1Mutation , err := helpers . FloorFloat64ToInt ( probabilityOf1Mutation * 100 )
if ( err != nil ) { return 0 , 0 , 0 , err }
percentageProbabilityOf2Mutations , err := helpers . FloorFloat64ToInt ( probabilityOf2Mutations * 100 )
if ( err != nil ) { return 0 , 0 , 0 , err }
return percentageProbabilityOf0Mutations , percentageProbabilityOf1Mutation , percentageProbabilityOf2Mutations , nil
}
2024-06-02 10:43:39 +02:00
// Outputs:
// -float64: Best Case Person 1 will pass variant probability (0-1)
// -float64: Worst Case Person 1 will pass variant probability (0-1)
// -float64: Best Case Person 2 will pass variant probability (0-1)
// -float64: Worst Case Person 2 will pass variant probability (0-1)
getBestAndWorstCaseProbabilities := func ( ) ( float64 , float64 , float64 , float64 ) {
2024-04-11 15:51:56 +02:00
2024-06-02 10:43:39 +02:00
if ( person1VariantProbabilityIsKnown == true && person2VariantProbabilityIsKnown == false ) {
2024-04-11 15:51:56 +02:00
2024-06-02 10:43:39 +02:00
return person1WillPassVariantProbability , person1WillPassVariantProbability , float64 ( 0 ) , float64 ( 1 )
}
if ( person1VariantProbabilityIsKnown == false && person2VariantProbabilityIsKnown == true ) {
return float64 ( 0 ) , float64 ( 1 ) , person2WillPassVariantProbability , person2WillPassVariantProbability
}
2024-04-11 15:51:56 +02:00
2024-06-02 10:43:39 +02:00
// Both people's probabilities are known
return person1WillPassVariantProbability , person1WillPassVariantProbability , person2WillPassVariantProbability , person2WillPassVariantProbability
2024-04-11 15:51:56 +02:00
}
2024-06-02 10:43:39 +02:00
bestCasePerson1WillPassVariantProbability , worstCasePerson1WillPassVariantProbability , bestCasePerson2WillPassVariantProbability , worstCasePerson2WillPassVariantProbability := getBestAndWorstCaseProbabilities ( )
2024-04-11 15:51:56 +02:00
2024-06-02 10:43:39 +02:00
bestCase0MutationsProbability , bestCase1MutationProbability , bestCase2MutationsProbability , err := getOffspringVariantProbabilities ( bestCasePerson1WillPassVariantProbability , bestCasePerson2WillPassVariantProbability )
if ( err != nil ) { return false , 0 , 0 , 0 , 0 , 0 , 0 , err }
2024-04-11 15:51:56 +02:00
2024-06-02 10:43:39 +02:00
worstCase0MutationsProbability , worstCase1MutationProbability , worstCase2MutationsProbability , err := getOffspringVariantProbabilities ( worstCasePerson1WillPassVariantProbability , worstCasePerson2WillPassVariantProbability )
if ( err != nil ) { return false , 0 , 0 , 0 , 0 , 0 , 0 , err }
2024-04-11 15:51:56 +02:00
2024-06-02 10:43:39 +02:00
// We have to figure out which 1-mutation-probability is lower
// The best case probabilities can actually result in a higher probability for 1 mutation
// than the worst case probabilities
// This is because in a worst-case-scenario, the probability of 1 mutation might be 0 because
// the probability of 2 mutations is 100
// This is not needed for the 0 and 2 mutation probabilities because TODO
2024-04-11 15:51:56 +02:00
2024-06-02 10:43:39 +02:00
lowerBound1MutationProbability := min ( bestCase1MutationProbability , worstCase1MutationProbability )
upperBound1MutationProbability := max ( bestCase1MutationProbability , worstCase1MutationProbability )
2024-04-11 15:51:56 +02:00
2024-06-02 10:43:39 +02:00
return true , worstCase0MutationsProbability , bestCase0MutationsProbability , lowerBound1MutationProbability , upperBound1MutationProbability , bestCase2MutationsProbability , worstCase2MutationsProbability , nil
2024-04-11 15:51:56 +02:00
}
probabilitiesKnown , probabilityOf0MutationsLowerBound , probabilityOf0MutationsUpperBound , probabilityOf1MutationLowerBound , probabilityOf1MutationUpperBound , probabilityOf2MutationsLowerBound , probabilityOf2MutationsUpperBound , err := getOffspringVariantProbabilities ( )
if ( err != nil ) { return err }
if ( probabilitiesKnown == false ) {
2024-06-02 10:43:39 +02:00
continue
}
2024-04-11 15:51:56 +02:00
2024-06-02 10:43:39 +02:00
newOffspringMonogenicDiseaseVariantInfoObject := geneticAnalysis . OffspringMonogenicDiseaseVariantInfo {
ProbabilityOf0MutationsLowerBound : probabilityOf0MutationsLowerBound ,
ProbabilityOf0MutationsUpperBound : probabilityOf0MutationsUpperBound ,
ProbabilityOf1MutationLowerBound : probabilityOf1MutationLowerBound ,
ProbabilityOf1MutationUpperBound : probabilityOf1MutationUpperBound ,
ProbabilityOf2MutationsLowerBound : probabilityOf2MutationsLowerBound ,
ProbabilityOf2MutationsUpperBound : probabilityOf2MutationsUpperBound ,
2024-04-11 15:51:56 +02:00
}
2024-06-02 10:43:39 +02:00
offspringVariantsInfoMap [ variantIdentifier ] = newOffspringMonogenicDiseaseVariantInfoObject
}
2024-04-11 15:51:56 +02:00
2024-06-02 10:43:39 +02:00
if ( person1ProbabilityIsKnown == false && person2ProbabilityIsKnown == false && len ( offspringVariantsInfoMap ) == 0 ) {
// We have no information about this genome pair's disease risk
// We don't add this genome pair's disease info to the diseaseInfoMap
2024-04-11 15:51:56 +02:00
return nil
}
2024-06-02 10:43:39 +02:00
numberOfVariantsTestedMap [ person1GenomeIdentifier ] = person1NumberOfVariantsTested
numberOfVariantsTestedMap [ person2GenomeIdentifier ] = person2NumberOfVariantsTested
2024-04-11 15:51:56 +02:00
2024-06-02 10:43:39 +02:00
newOffspringGenomePairMonogenicDiseaseInfoObject := geneticAnalysis . OffspringGenomePairMonogenicDiseaseInfo {
ProbabilityOffspringHasDiseaseIsKnown : offspringHasDiseaseProbabilityIsKnown ,
ProbabilityOffspringHasDisease : percentageProbabilityOffspringHasDisease ,
ProbabilityOffspringHasVariantIsKnown : offspringHasAVariantProbabilityIsKnown ,
ProbabilityOffspringHasVariant : percentageProbabilityOffspringHasVariant ,
VariantsInfoMap : offspringVariantsInfoMap ,
2024-04-11 15:51:56 +02:00
}
2024-06-02 10:43:39 +02:00
genomePairIdentifier := helpers . JoinTwo16ByteArrays ( person1GenomeIdentifier , person2GenomeIdentifier )
offspringMonogenicDiseaseInfoMap [ genomePairIdentifier ] = newOffspringGenomePairMonogenicDiseaseInfoObject
return nil
}
err = addGenomePairInfoToDiseaseMaps ( pair1Person1GenomeIdentifier , pair1Person2GenomeIdentifier )
if ( err != nil ) { return false , "" , err }
if ( genomePair2Exists == true ) {
err := addGenomePairInfoToDiseaseMaps ( pair2Person1GenomeIdentifier , pair2Person2GenomeIdentifier )
if ( err != nil ) { return false , "" , err }
}
newOffspringMonogenicDiseaseInfoObject := geneticAnalysis . OffspringMonogenicDiseaseInfo {
NumberOfVariantsTestedMap : numberOfVariantsTestedMap ,
MonogenicDiseaseInfoMap : offspringMonogenicDiseaseInfoMap ,
2024-04-11 15:51:56 +02:00
}
// Now we check for conflicts in monogenic disease results
// For couples, a conflict is when either genome pair has differing results for any disease probability/variant probability
// This is different from a person having conflicts within their different genomes
// Each genome pair only uses 1 genome from each person.
2024-06-02 10:43:39 +02:00
if ( len ( offspringMonogenicDiseaseInfoMap ) >= 2 ) {
2024-04-11 15:51:56 +02:00
2024-06-02 10:43:39 +02:00
// Conflicts are only possible if two genome pairs with information about this disease exist
2024-04-11 15:51:56 +02:00
checkIfConflictExists := func ( ) ( bool , error ) {
2024-06-02 10:43:39 +02:00
probabilityOffspringHasDiseaseIsKnown := false
probabilityOffspringHasDisease := 0
probabilityOffspringHasVariantIsKnown := false
probabilityOffspringHasVariant := 0
2024-04-11 15:51:56 +02:00
2024-06-02 10:43:39 +02:00
offspringVariantsInfoMap := make ( map [ [ 3 ] byte ] geneticAnalysis . OffspringMonogenicDiseaseVariantInfo )
2024-04-11 15:51:56 +02:00
2024-06-02 10:43:39 +02:00
firstItemReached := false
2024-04-11 15:51:56 +02:00
2024-06-02 10:43:39 +02:00
for _ , offspringMonogenicDiseaseInfoObject := range offspringMonogenicDiseaseInfoMap {
2024-04-11 15:51:56 +02:00
2024-06-02 10:43:39 +02:00
currentProbabilityOffspringHasDiseaseIsKnown := offspringMonogenicDiseaseInfoObject . ProbabilityOffspringHasDiseaseIsKnown
currentProbabilityOffspringHasDisease := offspringMonogenicDiseaseInfoObject . ProbabilityOffspringHasDisease
currentProbabilityOffspringHasVariantIsKnown := offspringMonogenicDiseaseInfoObject . ProbabilityOffspringHasVariantIsKnown
currentProbabilityOffspringHasVariant := offspringMonogenicDiseaseInfoObject . ProbabilityOffspringHasVariant
2024-04-11 15:51:56 +02:00
2024-06-02 10:43:39 +02:00
currentOffspringVariantsInfoMap := offspringMonogenicDiseaseInfoObject . VariantsInfoMap
if ( firstItemReached == false ) {
probabilityOffspringHasDiseaseIsKnown = currentProbabilityOffspringHasDiseaseIsKnown
2024-04-11 15:51:56 +02:00
probabilityOffspringHasDisease = currentProbabilityOffspringHasDisease
2024-06-02 10:43:39 +02:00
probabilityOffspringHasVariantIsKnown = currentProbabilityOffspringHasVariantIsKnown
2024-04-11 15:51:56 +02:00
probabilityOffspringHasVariant = currentProbabilityOffspringHasVariant
2024-06-02 10:43:39 +02:00
offspringVariantsInfoMap = currentOffspringVariantsInfoMap
firstItemReached = true
2024-04-11 15:51:56 +02:00
continue
}
2024-06-02 10:43:39 +02:00
if ( currentProbabilityOffspringHasDiseaseIsKnown != probabilityOffspringHasDiseaseIsKnown ) {
return true , nil
}
2024-04-11 15:51:56 +02:00
if ( currentProbabilityOffspringHasDisease != probabilityOffspringHasDisease ) {
return true , nil
}
2024-06-02 10:43:39 +02:00
if ( currentProbabilityOffspringHasVariantIsKnown != probabilityOffspringHasVariantIsKnown ) {
return true , nil
}
2024-04-11 15:51:56 +02:00
if ( currentProbabilityOffspringHasVariant != probabilityOffspringHasVariant ) {
return true , nil
}
2024-06-02 10:43:39 +02:00
areEqual := maps . Equal ( currentOffspringVariantsInfoMap , offspringVariantsInfoMap )
if ( areEqual == false ) {
return true , nil
2024-04-11 15:51:56 +02:00
}
}
return false , nil
}
conflictExists , err := checkIfConflictExists ( )
if ( err != nil ) { return false , "" , err }
2024-06-02 10:43:39 +02:00
newOffspringMonogenicDiseaseInfoObject . ConflictExists = conflictExists
2024-04-11 15:51:56 +02:00
}
2024-06-02 10:43:39 +02:00
offspringMonogenicDiseasesMap [ diseaseName ] = newOffspringMonogenicDiseaseInfoObject
2024-04-11 15:51:56 +02:00
}
2024-06-02 10:43:39 +02:00
newCoupleAnalysis . MonogenicDiseasesMap = offspringMonogenicDiseasesMap
2024-04-11 15:51:56 +02:00
// Step 2: Polygenic Diseases
polygenicDiseaseObjectsList , err := polygenicDiseases . GetPolygenicDiseaseObjectsList ( )
if ( err != nil ) { return false , "" , err }
2024-06-02 10:43:39 +02:00
// Map Structure: Disease Name -> OffspringPolygenicDiseaseInfo
offspringPolygenicDiseasesMap := make ( map [ string ] geneticAnalysis . OffspringPolygenicDiseaseInfo )
2024-04-11 15:51:56 +02:00
for _ , diseaseObject := range polygenicDiseaseObjectsList {
diseaseName := diseaseObject . DiseaseName
diseaseLociList := diseaseObject . LociList
2024-06-02 10:43:39 +02:00
person1DiseaseAnalysisObject , err := getPersonPolygenicDiseaseAnalysis ( person1GenomesWithMetadataList , diseaseObject )
2024-04-11 15:51:56 +02:00
if ( err != nil ) { return false , "" , err }
2024-06-02 10:43:39 +02:00
person2DiseaseAnalysisObject , err := getPersonPolygenicDiseaseAnalysis ( person2GenomesWithMetadataList , diseaseObject )
2024-04-11 15:51:56 +02:00
if ( err != nil ) { return false , "" , err }
2024-06-02 10:43:39 +02:00
// This map stores the polygenic disease info for each genome pair
// Map Structure: Genome Pair Identifier -> OffspringGenomePairPolygenicDiseaseInfo
offspringPolygenicDiseaseInfoMap := make ( map [ [ 32 ] byte ] geneticAnalysis . OffspringGenomePairPolygenicDiseaseInfo )
2024-04-11 15:51:56 +02:00
2024-06-02 10:43:39 +02:00
// This will calculate the disease info for the offspring from the two specified genomes to the diseases map
// It then adds the pair entry to the offspringPolygenicDiseaseInfoMap
addGenomePairDiseaseInfoToDiseaseMap := func ( person1GenomeIdentifier [ 16 ] byte , person2GenomeIdentifier [ 16 ] byte ) error {
2024-04-11 15:51:56 +02:00
2024-06-02 10:43:39 +02:00
summedRiskWeights := 0
minimumPossibleRiskWeightSum := 0
maximumPossibleRiskWeightSum := 0
2024-04-11 15:51:56 +02:00
2024-06-02 10:43:39 +02:00
// Map Structure: Locus Identifier -> OffspringPolygenicDiseaseLocusInfo
offspringDiseaseLociInfoMap := make ( map [ [ 3 ] byte ] geneticAnalysis . OffspringPolygenicDiseaseLocusInfo )
2024-04-11 15:51:56 +02:00
2024-06-02 10:43:39 +02:00
for _ , locusObject := range diseaseLociList {
2024-04-11 15:51:56 +02:00
2024-06-02 10:43:39 +02:00
locusIdentifierHex := locusObject . LocusIdentifier
2024-04-11 15:51:56 +02:00
2024-06-02 10:43:39 +02:00
locusIdentifier , err := encoding . DecodeHexStringTo3ByteArray ( locusIdentifierHex )
if ( err != nil ) { return err }
2024-04-11 15:51:56 +02:00
2024-06-02 10:43:39 +02:00
locusRiskWeightsMap := locusObject . RiskWeightsMap
locusOddsRatiosMap := locusObject . OddsRatiosMap
2024-04-11 15:51:56 +02:00
2024-06-02 10:43:39 +02:00
//Outputs:
// -bool: Locus value exists
// -string: Base1 Value
// -string: Base2 Value
// -error
getPersonGenomeLocusValues := func ( personGenomeIdentifier [ 16 ] byte , personDiseaseAnalysisObject geneticAnalysis . PersonPolygenicDiseaseInfo ) ( bool , string , string , error ) {
2024-04-11 15:51:56 +02:00
2024-06-02 10:43:39 +02:00
personPolygenicDiseaseInfoMap := personDiseaseAnalysisObject . PolygenicDiseaseInfoMap
2024-04-11 15:51:56 +02:00
2024-06-02 10:43:39 +02:00
personGenomeDiseaseInfoObject , exists := personPolygenicDiseaseInfoMap [ personGenomeIdentifier ]
if ( exists == false ) {
// This person's genome has no information about loci related to this disease
return false , "" , "" , nil
}
2024-04-11 15:51:56 +02:00
2024-06-02 10:43:39 +02:00
personGenomeLociMap := personGenomeDiseaseInfoObject . LociInfoMap
2024-04-11 15:51:56 +02:00
2024-06-02 10:43:39 +02:00
personGenomeLocusInfoObject , exists := personGenomeLociMap [ locusIdentifier ]
if ( exists == false ) {
// This person's genome doesn't have information about this locus
return false , "" , "" , nil
2024-04-11 15:51:56 +02:00
}
2024-06-02 10:43:39 +02:00
locusBase1 := personGenomeLocusInfoObject . LocusBase1
locusBase2 := personGenomeLocusInfoObject . LocusBase2
2024-04-11 15:51:56 +02:00
2024-06-02 10:43:39 +02:00
return true , locusBase1 , locusBase2 , nil
}
2024-04-11 15:51:56 +02:00
2024-06-02 10:43:39 +02:00
person1LocusBasePairKnown , person1LocusBase1 , person1LocusBase2 , err := getPersonGenomeLocusValues ( person1GenomeIdentifier , person1DiseaseAnalysisObject )
2024-04-11 15:51:56 +02:00
if ( err != nil ) { return err }
2024-06-02 10:43:39 +02:00
if ( person1LocusBasePairKnown == false ) {
// Offspring's disease info for this locus on this genome pair is unknown
continue
}
2024-04-11 15:51:56 +02:00
2024-06-02 10:43:39 +02:00
person2LocusBasePairKnown , person2LocusBase1 , person2LocusBase2 , err := getPersonGenomeLocusValues ( person2GenomeIdentifier , person2DiseaseAnalysisObject )
2024-04-11 15:51:56 +02:00
if ( err != nil ) { return err }
2024-06-02 10:43:39 +02:00
if ( person2LocusBasePairKnown == false ) {
// Offspring's disease info for this locus on this genome pair is unknown
continue
2024-04-11 15:51:56 +02:00
}
2024-06-02 10:43:39 +02:00
offspringAverageRiskWeight , offspringOddsRatioIsKnown , offspringAverageOddsRatio , averageUnknownOddsRatiosWeightSum , err := GetOffspringPolygenicDiseaseLocusInfo ( locusRiskWeightsMap , locusOddsRatiosMap , person1LocusBase1 , person1LocusBase2 , person2LocusBase1 , person2LocusBase2 )
2024-04-11 15:51:56 +02:00
if ( err != nil ) { return err }
2024-06-02 10:43:39 +02:00
newOffspringPolygenicDiseaseLocusInfo := geneticAnalysis . OffspringPolygenicDiseaseLocusInfo {
OffspringRiskWeight : offspringAverageRiskWeight ,
OffspringUnknownOddsRatiosWeightSum : averageUnknownOddsRatiosWeightSum ,
}
2024-04-11 15:51:56 +02:00
2024-06-02 10:43:39 +02:00
if ( offspringOddsRatioIsKnown == true ) {
2024-04-11 15:51:56 +02:00
2024-06-02 10:43:39 +02:00
newOffspringPolygenicDiseaseLocusInfo . OffspringOddsRatioIsKnown = true
newOffspringPolygenicDiseaseLocusInfo . OffspringOddsRatio = offspringAverageOddsRatio
}
2024-04-11 15:51:56 +02:00
2024-06-02 10:43:39 +02:00
offspringDiseaseLociInfoMap [ locusIdentifier ] = newOffspringPolygenicDiseaseLocusInfo
2024-04-11 15:51:56 +02:00
2024-06-02 10:43:39 +02:00
// Now we add risk weight info for this locus
2024-04-11 15:51:56 +02:00
2024-06-02 10:43:39 +02:00
locusMinimumRiskWeight := locusObject . MinimumRiskWeight
locusMaximumRiskWeight := locusObject . MaximumRiskWeight
2024-04-11 15:51:56 +02:00
2024-06-02 10:43:39 +02:00
minimumPossibleRiskWeightSum += locusMinimumRiskWeight
maximumPossibleRiskWeightSum += locusMaximumRiskWeight
2024-04-11 15:51:56 +02:00
2024-06-02 10:43:39 +02:00
summedRiskWeights += offspringAverageRiskWeight
}
2024-04-11 15:51:56 +02:00
2024-06-02 10:43:39 +02:00
numberOfLociTested := len ( offspringDiseaseLociInfoMap )
2024-04-11 15:51:56 +02:00
2024-06-02 10:43:39 +02:00
if ( numberOfLociTested == 0 ) {
// We have no information about this genome pair's disease risk
// We don't add this genome pair's disease info to the diseaseInfoMap
2024-04-11 15:51:56 +02:00
return nil
}
2024-06-02 10:43:39 +02:00
genomePairDiseaseRiskScore , err := helpers . ScaleNumberProportionally ( true , summedRiskWeights , minimumPossibleRiskWeightSum , maximumPossibleRiskWeightSum , 0 , 10 )
if ( err != nil ) { return err }
2024-04-11 15:51:56 +02:00
2024-06-02 10:43:39 +02:00
newOffspringGenomePairPolygenicDiseaseInfo := geneticAnalysis . OffspringGenomePairPolygenicDiseaseInfo {
2024-04-11 15:51:56 +02:00
2024-06-02 10:43:39 +02:00
NumberOfLociTested : numberOfLociTested ,
OffspringRiskScore : genomePairDiseaseRiskScore ,
LociInfoMap : offspringDiseaseLociInfoMap ,
2024-04-11 15:51:56 +02:00
}
2024-06-02 10:43:39 +02:00
genomePairIdentifier := helpers . JoinTwo16ByteArrays ( person1GenomeIdentifier , person2GenomeIdentifier )
2024-04-11 15:51:56 +02:00
2024-06-02 10:43:39 +02:00
offspringPolygenicDiseaseInfoMap [ genomePairIdentifier ] = newOffspringGenomePairPolygenicDiseaseInfo
2024-04-11 15:51:56 +02:00
2024-06-02 10:43:39 +02:00
return nil
2024-04-11 15:51:56 +02:00
}
2024-06-02 10:43:39 +02:00
err = addGenomePairDiseaseInfoToDiseaseMap ( pair1Person1GenomeIdentifier , pair1Person2GenomeIdentifier )
2024-04-11 15:51:56 +02:00
if ( err != nil ) { return false , "" , err }
2024-06-02 10:43:39 +02:00
if ( genomePair2Exists == true ) {
2024-04-11 15:51:56 +02:00
2024-06-02 10:43:39 +02:00
err := addGenomePairDiseaseInfoToDiseaseMap ( pair2Person1GenomeIdentifier , pair2Person2GenomeIdentifier )
if ( err != nil ) { return false , "" , err }
}
2024-04-11 15:51:56 +02:00
2024-06-02 10:43:39 +02:00
newOffspringPolygenicDiseaseInfoObject := geneticAnalysis . OffspringPolygenicDiseaseInfo {
PolygenicDiseaseInfoMap : offspringPolygenicDiseaseInfoMap ,
}
2024-04-11 15:51:56 +02:00
2024-06-02 10:43:39 +02:00
if ( len ( offspringPolygenicDiseaseInfoMap ) >= 2 ) {
2024-04-11 15:51:56 +02:00
2024-06-02 10:43:39 +02:00
// We check for conflicts
// Conflicts are only possible if two genome pairs with disease info for this disease exist
2024-04-11 15:51:56 +02:00
2024-06-02 10:43:39 +02:00
checkIfConflictExists := func ( ) ( bool , error ) {
2024-04-11 15:51:56 +02:00
2024-06-02 10:43:39 +02:00
numberOfLociTested := 0
offspringRiskScore := 0
offspringLociInfoMap := make ( map [ [ 3 ] byte ] geneticAnalysis . OffspringPolygenicDiseaseLocusInfo )
2024-04-11 15:51:56 +02:00
2024-06-02 10:43:39 +02:00
firstItemReached := false
2024-04-11 15:51:56 +02:00
2024-06-02 10:43:39 +02:00
for _ , genomePairDiseaseInfoObject := range offspringPolygenicDiseaseInfoMap {
2024-04-11 15:51:56 +02:00
2024-06-02 10:43:39 +02:00
genomePairNumberOfLociTested := genomePairDiseaseInfoObject . NumberOfLociTested
genomePairOffspringRiskScore := genomePairDiseaseInfoObject . OffspringRiskScore
genomePairLociInfoMap := genomePairDiseaseInfoObject . LociInfoMap
2024-04-11 15:51:56 +02:00
2024-06-02 10:43:39 +02:00
if ( firstItemReached == false ) {
numberOfLociTested = genomePairNumberOfLociTested
offspringRiskScore = genomePairOffspringRiskScore
offspringLociInfoMap = genomePairLociInfoMap
firstItemReached = true
continue
}
if ( numberOfLociTested != genomePairNumberOfLociTested ) {
return true , nil
}
if ( offspringRiskScore != genomePairOffspringRiskScore ) {
return true , nil
}
areEqual := maps . Equal ( offspringLociInfoMap , genomePairLociInfoMap )
if ( areEqual == false ) {
// A conflict exists
return true , nil
2024-04-11 15:51:56 +02:00
}
}
return false , nil
}
conflictExists , err := checkIfConflictExists ( )
if ( err != nil ) { return false , "" , err }
2024-06-02 10:43:39 +02:00
newOffspringPolygenicDiseaseInfoObject . ConflictExists = conflictExists
2024-04-11 15:51:56 +02:00
}
2024-06-02 10:43:39 +02:00
offspringPolygenicDiseasesMap [ diseaseName ] = newOffspringPolygenicDiseaseInfoObject
2024-04-11 15:51:56 +02:00
}
2024-06-02 10:43:39 +02:00
newCoupleAnalysis . PolygenicDiseasesMap = offspringPolygenicDiseasesMap
2024-04-11 15:51:56 +02:00
// Step 3: Traits
traitObjectsList , err := traits . GetTraitObjectsList ( )
if ( err != nil ) { return false , "" , err }
2024-06-02 10:43:39 +02:00
// Map Structure: Trait Name -> Trait Info Object
offspringTraitsMap := make ( map [ string ] geneticAnalysis . OffspringTraitInfo )
2024-04-11 15:51:56 +02:00
for _ , traitObject := range traitObjectsList {
traitName := traitObject . TraitName
traitRulesList := traitObject . RulesList
2024-06-02 10:43:39 +02:00
person1TraitAnalysisObject , err := getPersonTraitAnalysis ( person1GenomesWithMetadataList , traitObject )
2024-04-11 15:51:56 +02:00
if ( err != nil ) { return false , "" , err }
2024-06-02 10:43:39 +02:00
person2TraitAnalysisObject , err := getPersonTraitAnalysis ( person2GenomesWithMetadataList , traitObject )
2024-04-11 15:51:56 +02:00
if ( err != nil ) { return false , "" , err }
2024-06-02 10:43:39 +02:00
// This map stores the trait info for each genome pair
// Map Structure: Genome Pair Identifier -> OffspringGenomePairTraitInfo
offspringTraitInfoMap := make ( map [ [ 32 ] byte ] geneticAnalysis . OffspringGenomePairTraitInfo )
2024-04-11 15:51:56 +02:00
2024-06-02 10:43:39 +02:00
// This will add the offspring trait information for the provided genome pair to the offspringTraitInfoMap
addGenomePairTraitInfoToOffspringMap := func ( person1GenomeIdentifier [ 16 ] byte , person2GenomeIdentifier [ 16 ] byte ) error {
2024-04-11 15:51:56 +02:00
2024-06-02 10:43:39 +02:00
// Map Structure: Outcome Name -> Outcome Score
// Example: "Intolerant" -> 2.5
offspringAverageOutcomeScoresMap := make ( map [ string ] float64 )
2024-04-11 15:51:56 +02:00
2024-06-02 10:43:39 +02:00
// Map Structure: Rule Identifier -> Offspring Probability Of Passing Rule
// The value stores the probability that the offspring will pass the rule
// This is a number between 0-100%
offspringProbabilityOfPassingRulesMap := make ( map [ [ 3 ] byte ] int )
2024-04-11 15:51:56 +02:00
2024-06-02 10:43:39 +02:00
// We iterate through rules to determine genome pair trait info
2024-04-11 15:51:56 +02:00
2024-06-02 10:43:39 +02:00
for _ , ruleObject := range traitRulesList {
2024-04-11 15:51:56 +02:00
2024-06-02 10:43:39 +02:00
ruleIdentifierHex := ruleObject . RuleIdentifier
2024-04-11 15:51:56 +02:00
2024-06-02 10:43:39 +02:00
ruleIdentifier , err := encoding . DecodeHexStringTo3ByteArray ( ruleIdentifierHex )
if ( err != nil ) { return err }
2024-04-11 15:51:56 +02:00
2024-06-02 10:43:39 +02:00
// This is a list that describes the locus rsids and their values that must be fulfilled to pass the rule
ruleLocusObjectsList := ruleObject . LociList
2024-04-11 15:51:56 +02:00
2024-06-02 10:43:39 +02:00
//Outputs:
// -bool: Any rule loci are known
// -map[int64]locusValue.LocusValue: rsID -> Locus base pair value
// -error
getPersonGenomeTraitLociValuesMap := func ( personGenomeIdentifier [ 16 ] byte , personTraitAnalysisObject geneticAnalysis . PersonTraitInfo ) ( bool , map [ int64 ] locusValue . LocusValue , error ) {
2024-04-11 15:51:56 +02:00
2024-06-02 10:43:39 +02:00
personTraitInfoMap := personTraitAnalysisObject . TraitInfoMap
2024-04-11 15:51:56 +02:00
2024-06-02 10:43:39 +02:00
personGenomeTraitInfoObject , exists := personTraitInfoMap [ personGenomeIdentifier ]
if ( exists == false ) {
// This person has no genome values for any loci for this trait
return false , nil , nil
2024-04-11 15:51:56 +02:00
}
2024-06-02 10:43:39 +02:00
personLocusValuesMap := personGenomeTraitInfoObject . LocusValuesMap
2024-04-11 15:51:56 +02:00
2024-06-02 10:43:39 +02:00
return true , personLocusValuesMap , nil
}
2024-04-11 15:51:56 +02:00
2024-06-02 10:43:39 +02:00
anyPerson1LociKnown , person1GenomeTraitLociValuesMap , err := getPersonGenomeTraitLociValuesMap ( person1GenomeIdentifier , person1TraitAnalysisObject )
2024-04-11 15:51:56 +02:00
if ( err != nil ) { return err }
2024-06-02 10:43:39 +02:00
if ( anyPerson1LociKnown == false ) {
2024-04-11 15:51:56 +02:00
// We only know how many of the 4 prospective offspring pass the rule if all loci are known for both people's genomes
return nil
}
2024-06-02 10:43:39 +02:00
anyPerson2LociKnown , person2GenomeTraitLociValuesMap , err := getPersonGenomeTraitLociValuesMap ( person2GenomeIdentifier , person2TraitAnalysisObject )
if ( err != nil ) { return err }
if ( anyPerson2LociKnown == false ) {
// We only know how many of the 4 prospective offspring pass the rule if all loci are known for both people's genomes
return nil
2024-04-11 15:51:56 +02:00
}
2024-06-02 10:43:39 +02:00
getOffspringProbabilityOfPassingRule := func ( ) ( bool , int , error ) {
2024-04-11 15:51:56 +02:00
2024-06-02 10:43:39 +02:00
// This is a probability between 0 and 1
offspringProbabilityOfPassingRule := float64 ( 1 )
2024-04-11 15:51:56 +02:00
2024-06-02 10:43:39 +02:00
for _ , ruleLocusObject := range ruleLocusObjectsList {
2024-04-11 15:51:56 +02:00
2024-06-02 10:43:39 +02:00
locusRSID := ruleLocusObject . LocusRSID
2024-04-11 15:51:56 +02:00
2024-06-02 10:43:39 +02:00
person1LocusValue , exists := person1GenomeTraitLociValuesMap [ locusRSID ]
if ( exists == false ) {
// We don't know the locus value for this rule for this person
// Thus, we cannot calculate the probabilityOfPassingRule for the offspring
return false , 0 , nil
}
2024-04-11 15:51:56 +02:00
2024-06-02 10:43:39 +02:00
person2LocusValue , exists := person2GenomeTraitLociValuesMap [ locusRSID ]
if ( exists == false ) {
// We don't know the locus value for this rule for this person
// Thus, we cannot calculate the probabilityOfPassingRule for the offspring
return false , 0 , nil
}
2024-04-11 15:51:56 +02:00
2024-06-02 10:43:39 +02:00
locusRequiredBasePairsList := ruleLocusObject . BasePairsList
2024-04-11 15:51:56 +02:00
2024-06-02 10:43:39 +02:00
person1LocusBase1Value := person1LocusValue . Base1Value
person1LocusBase2Value := person1LocusValue . Base2Value
2024-04-11 15:51:56 +02:00
2024-06-02 10:43:39 +02:00
person2LocusBase1Value := person2LocusValue . Base1Value
person2LocusBase2Value := person2LocusValue . Base2Value
2024-04-11 15:51:56 +02:00
2024-06-02 10:43:39 +02:00
offspringProbabilityOfPassingRuleLocus , err := GetOffspringTraitRuleLocusInfo ( locusRequiredBasePairsList , person1LocusBase1Value , person1LocusBase2Value , person2LocusBase1Value , person2LocusBase2Value )
if ( err != nil ) { return false , 0 , err }
2024-04-11 15:51:56 +02:00
2024-06-02 10:43:39 +02:00
offspringProbabilityOfPassingRule *= offspringProbabilityOfPassingRuleLocus
}
2024-04-11 15:51:56 +02:00
2024-06-02 10:43:39 +02:00
offspringPercentageProbabilityOfPassingRule := offspringProbabilityOfPassingRule * 100
2024-04-11 15:51:56 +02:00
2024-06-02 10:43:39 +02:00
probabilityRounded , err := helpers . FloorFloat64ToInt ( offspringPercentageProbabilityOfPassingRule )
if ( err != nil ) { return false , 0 , err }
2024-04-11 15:51:56 +02:00
2024-06-02 10:43:39 +02:00
return true , probabilityRounded , nil
2024-04-11 15:51:56 +02:00
}
2024-06-02 10:43:39 +02:00
ruleProbabilityIsKnown , offspringPercentageProbabilityOfPassingRule , err := getOffspringProbabilityOfPassingRule ( )
if ( err != nil ) { return err }
if ( ruleProbabilityIsKnown == false ) {
// We continue to the next rule
2024-04-11 15:51:56 +02:00
continue
}
2024-06-02 10:43:39 +02:00
offspringProbabilityOfPassingRulesMap [ ruleIdentifier ] = offspringPercentageProbabilityOfPassingRule
2024-04-11 15:51:56 +02:00
// This is the 0 - 1 probability value
2024-06-02 10:43:39 +02:00
offspringProbabilityOfPassingRule := float64 ( offspringPercentageProbabilityOfPassingRule ) / 100
2024-04-11 15:51:56 +02:00
ruleOutcomePointsMap := ruleObject . OutcomePointsMap
for outcomeName , outcomePointsEffect := range ruleOutcomePointsMap {
pointsToAdd := float64 ( outcomePointsEffect ) * offspringProbabilityOfPassingRule
2024-06-02 10:43:39 +02:00
offspringAverageOutcomeScoresMap [ outcomeName ] += pointsToAdd
2024-04-11 15:51:56 +02:00
}
}
2024-06-02 10:43:39 +02:00
numberOfRulesTested := len ( offspringProbabilityOfPassingRulesMap )
2024-04-11 15:51:56 +02:00
if ( numberOfRulesTested == 0 ) {
2024-06-02 10:43:39 +02:00
// No rules were tested for this trait
// We will not add anything to the trait info map for this genome pair
2024-04-11 15:51:56 +02:00
return nil
}
traitOutcomesList := traitObject . OutcomesList
2024-06-02 10:43:39 +02:00
// We add a 0 outcome for outcomes without any points
2024-04-11 15:51:56 +02:00
2024-06-02 10:43:39 +02:00
for _ , outcomeName := range traitOutcomesList {
2024-04-11 15:51:56 +02:00
2024-06-02 10:43:39 +02:00
_ , exists := offspringAverageOutcomeScoresMap [ outcomeName ]
if ( exists == false ) {
// No rules effected this outcome.
offspringAverageOutcomeScoresMap [ outcomeName ] = 0
2024-04-11 15:51:56 +02:00
}
2024-06-02 10:43:39 +02:00
}
2024-04-11 15:51:56 +02:00
2024-06-02 10:43:39 +02:00
newOffspringGenomePairTraitInfoObject := geneticAnalysis . OffspringGenomePairTraitInfo {
NumberOfRulesTested : numberOfRulesTested ,
OffspringAverageOutcomeScoresMap : offspringAverageOutcomeScoresMap ,
ProbabilityOfPassingRulesMap : offspringProbabilityOfPassingRulesMap ,
}
2024-04-11 15:51:56 +02:00
2024-06-02 10:43:39 +02:00
genomePairIdentifier := helpers . JoinTwo16ByteArrays ( person1GenomeIdentifier , person2GenomeIdentifier )
2024-04-11 15:51:56 +02:00
2024-06-02 10:43:39 +02:00
offspringTraitInfoMap [ genomePairIdentifier ] = newOffspringGenomePairTraitInfoObject
2024-04-11 15:51:56 +02:00
return nil
}
2024-06-02 10:43:39 +02:00
err = addGenomePairTraitInfoToOffspringMap ( pair1Person1GenomeIdentifier , pair1Person2GenomeIdentifier )
2024-04-11 15:51:56 +02:00
if ( err != nil ) { return false , "" , err }
if ( genomePair2Exists == true ) {
2024-06-02 10:43:39 +02:00
err := addGenomePairTraitInfoToOffspringMap ( pair2Person1GenomeIdentifier , pair2Person2GenomeIdentifier )
2024-04-11 15:51:56 +02:00
if ( err != nil ) { return false , "" , err }
}
2024-06-02 10:43:39 +02:00
newOffspringTraitInfoObject := geneticAnalysis . OffspringTraitInfo {
TraitInfoMap : offspringTraitInfoMap ,
}
if ( len ( offspringTraitInfoMap ) >= 2 ) {
2024-04-11 15:51:56 +02:00
// We check for conflicts
2024-06-02 10:43:39 +02:00
// Conflicts are only possible if two genome pairs exist with information about the trait
2024-04-11 15:51:56 +02:00
checkIfConflictExists := func ( ) ( bool , error ) {
2024-06-02 10:43:39 +02:00
// We check for conflicts between each genome pair's outcome scores and trait rules maps
2024-04-11 15:51:56 +02:00
2024-06-02 10:43:39 +02:00
offspringAverageOutcomeScoresMap := make ( map [ string ] float64 )
offspringProbabilityOfPassingRulesMap := make ( map [ [ 3 ] byte ] int )
2024-04-11 15:51:56 +02:00
2024-06-02 10:43:39 +02:00
firstItemReached := false
2024-04-11 15:51:56 +02:00
2024-06-02 10:43:39 +02:00
for _ , genomePairTraitInfoObject := range offspringTraitInfoMap {
2024-04-11 15:51:56 +02:00
2024-06-02 10:43:39 +02:00
currentOffspringAverageOutcomeScoresMap := genomePairTraitInfoObject . OffspringAverageOutcomeScoresMap
currentProbabilityOfPassingRulesMap := genomePairTraitInfoObject . ProbabilityOfPassingRulesMap
2024-04-11 15:51:56 +02:00
2024-06-02 10:43:39 +02:00
if ( firstItemReached == false ) {
offspringAverageOutcomeScoresMap = currentOffspringAverageOutcomeScoresMap
offspringProbabilityOfPassingRulesMap = currentProbabilityOfPassingRulesMap
2024-04-11 15:51:56 +02:00
2024-06-02 10:43:39 +02:00
firstItemReached = true
continue
}
2024-04-11 15:51:56 +02:00
2024-06-02 10:43:39 +02:00
areEqual := maps . Equal ( offspringAverageOutcomeScoresMap , currentOffspringAverageOutcomeScoresMap )
if ( areEqual == false ) {
return true , nil
}
areEqual = maps . Equal ( offspringProbabilityOfPassingRulesMap , currentProbabilityOfPassingRulesMap )
if ( areEqual == false ) {
return true , nil
2024-04-11 15:51:56 +02:00
}
}
return false , nil
}
conflictExists , err := checkIfConflictExists ( )
if ( err != nil ) { return false , "" , err }
2024-06-02 10:43:39 +02:00
newOffspringTraitInfoObject . ConflictExists = conflictExists
2024-04-11 15:51:56 +02:00
}
2024-06-02 10:43:39 +02:00
offspringTraitsMap [ traitName ] = newOffspringTraitInfoObject
2024-04-11 15:51:56 +02:00
}
2024-06-02 10:43:39 +02:00
newCoupleAnalysis . TraitsMap = offspringTraitsMap
analysisBytes , err := encoding . EncodeMessagePackBytes ( newCoupleAnalysis )
2024-04-11 15:51:56 +02:00
if ( err != nil ) { return false , "" , err }
analysisString := string ( analysisBytes )
return true , analysisString , nil
}
// We also use this function when calculating offspring probabilities between users in viewProfileGui.go
//Outputs:
2024-06-02 10:43:39 +02:00
// -bool: Probability offspring has disease is known
2024-04-11 15:51:56 +02:00
// -int: Percentage probability offspring has disease (0-100)
2024-06-02 10:43:39 +02:00
// -bool: Probability offspring has variant is known
2024-04-11 15:51:56 +02:00
// -int: Percentage probability offspring has variant (0-100)
// -error
2024-06-02 10:43:39 +02:00
func GetOffspringMonogenicDiseaseProbabilities ( dominantOrRecessive string , person1ProbabilityIsKnown bool , person1WillPassVariantPercentageProbability int , person2ProbabilityIsKnown bool , person2WillPassVariantPercentageProbability int ) ( bool , int , bool , int , error ) {
2024-04-11 15:51:56 +02:00
if ( dominantOrRecessive != "Dominant" && dominantOrRecessive != "Recessive" ) {
2024-06-02 10:43:39 +02:00
return false , 0 , false , 0 , errors . New ( "GetOffspringMonogenicDiseaseProbabilities called with invalid dominantOrRecessive: " + dominantOrRecessive )
}
if ( person1ProbabilityIsKnown == false && person2ProbabilityIsKnown == false ) {
return false , 0 , false , 0 , nil
2024-04-11 15:51:56 +02:00
}
2024-06-02 10:43:39 +02:00
if ( person1ProbabilityIsKnown == true ) {
if ( person1WillPassVariantPercentageProbability < 0 || person1WillPassVariantPercentageProbability > 100 ) {
return false , 0 , false , 0 , errors . New ( "GetOffspringMonogenicDiseaseProbabilities called with invalid person1WillPassVariantProbability" )
}
}
if ( person2ProbabilityIsKnown == true ) {
if ( person2WillPassVariantPercentageProbability < 0 || person2WillPassVariantPercentageProbability > 100 ) {
return false , 0 , false , 0 , errors . New ( "GetOffspringMonogenicDiseaseProbabilities called with invalid person2WillPassVariantProbability" )
}
2024-04-11 15:51:56 +02:00
}
2024-06-02 10:43:39 +02:00
if ( person1ProbabilityIsKnown == false || person2ProbabilityIsKnown == false ) {
// Only 1 of the person's probabilities are known
getPersonWhoHasVariantProbabilityOfPassingIt := func ( ) int {
if ( person1ProbabilityIsKnown == true ) {
return person1WillPassVariantPercentageProbability
}
return person2WillPassVariantPercentageProbability
}
2024-04-11 15:51:56 +02:00
2024-06-02 10:43:39 +02:00
personWhoHasVariantProbabilityOfPassingIt := getPersonWhoHasVariantProbabilityOfPassingIt ( )
if ( personWhoHasVariantProbabilityOfPassingIt == 100 ) {
if ( dominantOrRecessive == "Dominant" ) {
// We know the offspring will have the disease and will have a variant
return true , 100 , true , 100 , nil
}
//dominantOrRecessive == "Recessive"
// We don't know if the offspring will have the disease, but we know they will have a variant
return false , 0 , true , 100 , nil
}
if ( personWhoHasVariantProbabilityOfPassingIt == 0 ) {
if ( dominantOrRecessive == "Recessive" ) {
// We know the offspring will not have the disease, but we don't know if they will have a variant
return true , 0 , false , 0 , nil
}
}
// We don't know the probabilities that the offspring will have the disease or if they will have a variant
return false , 0 , false , 0 , nil
2024-04-11 15:51:56 +02:00
}
2024-06-02 10:43:39 +02:00
person1WillPassVariantProbability := float64 ( person1WillPassVariantPercentageProbability ) / 100
person2WillPassVariantProbability := float64 ( person2WillPassVariantPercentageProbability ) / 100
2024-04-11 15:51:56 +02:00
// The probability offspring has a variant = the probability that either parent passes a variant (inclusive or)
// We find the probability of the offspring having a monogenic disease variant as follows:
// P(A U B) = P(A) + P(B) - P(A ∩ B)
2024-06-02 10:43:39 +02:00
// (Probability of person 1 passing a variant) + (Probability of person 2 passing a variant) - (Probability of offspring having disease)
2024-04-11 15:51:56 +02:00
// A person with a variant may have the disease, or just be a carrier.
2024-06-02 10:43:39 +02:00
probabilityOffspringHasVariant := person1WillPassVariantProbability + person2WillPassVariantProbability - ( person1WillPassVariantProbability * person2WillPassVariantProbability )
2024-04-11 15:51:56 +02:00
if ( dominantOrRecessive == "Dominant" ) {
// The probability of having the monogenic disease is the same as the probability of having a variant
percentageProbabilityOffspringHasVariant := int ( probabilityOffspringHasVariant * 100 )
2024-06-02 10:43:39 +02:00
return true , percentageProbabilityOffspringHasVariant , true , percentageProbabilityOffspringHasVariant , nil
2024-04-11 15:51:56 +02:00
}
// We find the probability of the offspring having the mongenic disease as follows:
// P(A and B) = P(A) * P(B)
2024-06-02 10:43:39 +02:00
// (Probability of person 1 Passing a variant) * (Probability of person 2 passing a variant)
probabilityOffspringHasDisease := person1WillPassVariantProbability * person2WillPassVariantProbability
2024-04-11 15:51:56 +02:00
percentageProbabilityOffspringHasDisease := probabilityOffspringHasDisease * 100
percentageProbabilityOffspringHasVariant := probabilityOffspringHasVariant * 100
// This conversion remove any digits after the radix point
// This will not result in any false 0% values, an example being 0.9% becoming 0%
// This is because the lowest non-zero probability a person can have for passing a variant is 50%
// Thus, the lowest non-zero probability of an offspring having a disease is 25%
percentageProbabilityOffspringHasDiseaseInt := int ( percentageProbabilityOffspringHasDisease )
percentageProbabilityOffspringHasVariantInt := int ( percentageProbabilityOffspringHasVariant )
2024-06-02 10:43:39 +02:00
return true , percentageProbabilityOffspringHasDiseaseInt , true , percentageProbabilityOffspringHasVariantInt , nil
2024-04-11 15:51:56 +02:00
}
//Outputs:
// -int: Offspring average risk weight
// -bool: Odds ratio is known
// -float64: Offspring average odds ratio
// -int: Offspring unknown odds ratios weight sum
// -error
2024-06-02 10:43:39 +02:00
func GetOffspringPolygenicDiseaseLocusInfo ( locusRiskWeightsMap map [ string ] int , locusOddsRatiosMap map [ string ] float64 , person1LocusBase1 string , person1LocusBase2 string , person2LocusBase1 string , person2LocusBase2 string ) ( int , bool , float64 , int , error ) {
2024-04-11 15:51:56 +02:00
// We create the 4 options for the offspring's bases at this locus
2024-06-02 10:43:39 +02:00
offspringBasePairOutcome1 := person1LocusBase1 + ";" + person2LocusBase1
offspringBasePairOutcome2 := person1LocusBase2 + ";" + person2LocusBase2
offspringBasePairOutcome3 := person1LocusBase1 + ";" + person2LocusBase2
offspringBasePairOutcome4 := person1LocusBase2 + ";" + person2LocusBase1
2024-04-11 15:51:56 +02:00
2024-06-02 10:43:39 +02:00
baseOutcomesList := [ ] string { offspringBasePairOutcome1 , offspringBasePairOutcome2 , offspringBasePairOutcome3 , offspringBasePairOutcome4 }
2024-04-11 15:51:56 +02:00
summedRiskWeight := 0
numberOfSummedOddsRatios := 0
summedOddsRatios := float64 ( 0 )
numberOfSummedUnknownOddsRatioWeights := 0
summedUnknownOddsRatioWeights := 0
for _ , outcomeBasePair := range baseOutcomesList {
isValid := verifyBasePair ( outcomeBasePair )
if ( isValid == false ) {
return 0 , false , 0 , 0 , errors . New ( "GetOffspringPolygenicDiseaseLocusInfo called with invalid locus base pair: " + outcomeBasePair )
}
offspringOutcomeRiskWeight , exists := locusRiskWeightsMap [ outcomeBasePair ]
if ( exists == false ) {
// We do not know the risk weight for this base pair
// We treat this as a 0 risk for both weight and odds ratio
2024-06-02 10:43:39 +02:00
// No effect on risk is represented as an odds ratio of 1
2024-04-11 15:51:56 +02:00
summedOddsRatios += 1
numberOfSummedOddsRatios += 1
continue
}
summedRiskWeight += offspringOutcomeRiskWeight
offspringOutcomeOddsRatio , exists := locusOddsRatiosMap [ outcomeBasePair ]
if ( exists == false ) {
// This particular outcome has no known odds ratio
// We add it to the unknown odds ratio weights sum
summedUnknownOddsRatioWeights += offspringOutcomeRiskWeight
numberOfSummedUnknownOddsRatioWeights += 1
} else {
summedOddsRatios += offspringOutcomeOddsRatio
numberOfSummedOddsRatios += 1
}
}
averageRiskWeight := summedRiskWeight / 4
getAverageUnknownOddsRatiosWeightSum := func ( ) int {
if ( numberOfSummedUnknownOddsRatioWeights == 0 ) {
return 0
}
averageUnknownOddsRatiosWeightSum := summedUnknownOddsRatioWeights / numberOfSummedUnknownOddsRatioWeights
return averageUnknownOddsRatiosWeightSum
}
averageUnknownOddsRatiosWeightSum := getAverageUnknownOddsRatiosWeightSum ( )
if ( numberOfSummedOddsRatios == 0 ) {
return averageRiskWeight , false , 0 , averageUnknownOddsRatiosWeightSum , nil
}
averageOddsRatio := summedOddsRatios / float64 ( numberOfSummedOddsRatios )
return averageRiskWeight , true , averageOddsRatio , averageUnknownOddsRatiosWeightSum , nil
}
//Outputs:
// -float64: Probability of offspring passing rule (0-1)
// -error
2024-06-02 10:43:39 +02:00
func GetOffspringTraitRuleLocusInfo ( locusRequiredBasePairsList [ ] string , person1LocusBase1 string , person1LocusBase2 string , person2LocusBase1 string , person2LocusBase2 string ) ( float64 , error ) {
2024-04-11 15:51:56 +02:00
// We create the 4 options for the offspring's bases at this locus
2024-06-02 10:43:39 +02:00
offspringBasePairOutcome1 := person1LocusBase1 + ";" + person2LocusBase1
offspringBasePairOutcome2 := person1LocusBase2 + ";" + person2LocusBase2
offspringBasePairOutcome3 := person1LocusBase1 + ";" + person2LocusBase2
offspringBasePairOutcome4 := person1LocusBase2 + ";" + person2LocusBase1
2024-04-11 15:51:56 +02:00
2024-06-02 10:43:39 +02:00
baseOutcomesList := [ ] string { offspringBasePairOutcome1 , offspringBasePairOutcome2 , offspringBasePairOutcome3 , offspringBasePairOutcome4 }
2024-04-11 15:51:56 +02:00
numberOfOffspringOutcomesWhomPassRuleLocus := 0
for _ , outcomeBasePair := range baseOutcomesList {
isValid := verifyBasePair ( outcomeBasePair )
if ( isValid == false ) {
return 0 , errors . New ( "GetOffspringTraitRuleLocusInfo called with invalid locus base pair: " + outcomeBasePair )
}
outcomePassesRuleLocus := slices . Contains ( locusRequiredBasePairsList , outcomeBasePair )
if ( outcomePassesRuleLocus == true ) {
numberOfOffspringOutcomesWhomPassRuleLocus += 1
}
}
offspringProbabilityOfPassingRuleLocus := float64 ( numberOfOffspringOutcomesWhomPassRuleLocus ) / float64 ( 4 )
return offspringProbabilityOfPassingRuleLocus , nil
}
// This function will retrieve the base pair of the locus from the input genome map
// We need this because each rsID has aliases, so we must sometimes check those aliases to find locus values
//
// Outputs:
// -bool: Valid base pair value found
// -string: Base 1 Value (Nucleotide base for the SNP)
// -string: Base 2 Value (Nucleotide base for the SNP)
2024-06-02 10:43:39 +02:00
// -bool: Locus base pair is phased
2024-04-11 15:51:56 +02:00
// -error
func getGenomeLocusBasePair ( inputGenomeMap map [ int64 ] locusValue . LocusValue , locusRSID int64 ) ( bool , string , string , bool , error ) {
// Outputs:
// -bool: Locus value found
// -locusValue.LocusValue
// -error
getLocusValue := func ( ) ( bool , locusValue . LocusValue , error ) {
currentLocusValue , exists := inputGenomeMap [ locusRSID ]
if ( exists == true ) {
return true , currentLocusValue , nil
}
// We check for aliases
anyAliasesExist , rsidAliasesList , err := locusMetadata . GetRSIDAliases ( locusRSID )
if ( err != nil ) { return false , locusValue . LocusValue { } , err }
if ( anyAliasesExist == false ) {
return false , locusValue . LocusValue { } , nil
}
for _ , rsidAlias := range rsidAliasesList {
currentLocusValue , exists := inputGenomeMap [ rsidAlias ]
if ( exists == true ) {
return true , currentLocusValue , nil
}
}
return false , locusValue . LocusValue { } , nil
}
locusValueFound , locusValueObject , err := getLocusValue ( )
if ( err != nil ) { return false , "" , "" , false , err }
if ( locusValueFound == false ) {
return false , "" , "" , false , nil
}
base1Value := locusValueObject . Base1Value
base2Value := locusValueObject . Base2Value
locusIsPhased := locusValueObject . LocusIsPhased
return true , base1Value , base2Value , locusIsPhased , nil
}
//Outputs:
2024-06-02 10:43:39 +02:00
// -geneticAnalysis.PersonMonogenicDiseaseInfo: Monogenic disease analysis object
2024-04-11 15:51:56 +02:00
// -error
2024-06-02 10:43:39 +02:00
func getPersonMonogenicDiseaseAnalysis ( inputGenomesWithMetadataList [ ] prepareRawGenomes . GenomeWithMetadata , diseaseObject monogenicDiseases . MonogenicDisease ) ( geneticAnalysis . PersonMonogenicDiseaseInfo , error ) {
emptyDiseaseInfoObject := geneticAnalysis . PersonMonogenicDiseaseInfo { }
2024-04-11 15:51:56 +02:00
dominantOrRecessive := diseaseObject . DominantOrRecessive
variantsList := diseaseObject . VariantsList
// We use this map to keep track of which RSIDs corresponds to each variant
2024-06-02 10:43:39 +02:00
// We also use it to have a map of all variants for the disease
2024-04-11 15:51:56 +02:00
// Map Structure: Variant Identifier -> []rsID
2024-06-02 10:43:39 +02:00
variantRSIDsMap := make ( map [ [ 3 ] byte ] [ ] int64 )
2024-04-11 15:51:56 +02:00
2024-06-02 10:43:39 +02:00
// This map stores all rsIDs for this monogenic disease
// These are locations in the disease's gene which, if mutated, are known to cause the disease
// We use this map to avoid duplicate rsIDs, because one rsID can have multiple variants which belong to it
// We also store all alias rsIDs in this map
allRSIDsMap := make ( map [ int64 ] struct { } )
2024-04-11 15:51:56 +02:00
for _ , variantObject := range variantsList {
2024-06-02 10:43:39 +02:00
variantIdentifierHex := variantObject . VariantIdentifier
2024-04-11 15:51:56 +02:00
2024-06-02 10:43:39 +02:00
variantIdentifier , err := encoding . DecodeHexStringTo3ByteArray ( variantIdentifierHex )
if ( err != nil ) { return emptyDiseaseInfoObject , err }
2024-04-11 15:51:56 +02:00
variantRSID := variantObject . VariantRSID
variantRSIDsList := [ ] int64 { variantRSID }
// We add aliases to variantRSIDsList
anyAliasesExist , rsidAliasesList , err := locusMetadata . GetRSIDAliases ( variantRSID )
2024-06-02 10:43:39 +02:00
if ( err != nil ) { return emptyDiseaseInfoObject , err }
2024-04-11 15:51:56 +02:00
if ( anyAliasesExist == true ) {
variantRSIDsList = append ( variantRSIDsList , rsidAliasesList ... )
}
variantRSIDsMap [ variantIdentifier ] = variantRSIDsList
2024-06-02 10:43:39 +02:00
for _ , rsID := range variantRSIDsList {
allRSIDsMap [ rsID ] = struct { } { }
}
}
// Now we create a new map without any rsID aliases
// Each rsID in this map represents a unique locus on the genome
// Each rsID may have aliases, but they are not included in this map
allUniqueRSIDsMap := make ( map [ int64 ] struct { } )
for rsID , _ := range allRSIDsMap {
anyAliasesExist , rsidAliasesList , err := locusMetadata . GetRSIDAliases ( rsID )
if ( err != nil ) { return emptyDiseaseInfoObject , err }
if ( anyAliasesExist == false ) {
allUniqueRSIDsMap [ rsID ] = struct { } { }
continue
}
2024-04-11 15:51:56 +02:00
2024-06-02 10:43:39 +02:00
// We see if we already added an alias of this rsID to the map
checkIfAliasAlreadyExists := func ( ) bool {
for _ , rsIDAlias := range rsidAliasesList {
_ , exists := allUniqueRSIDsMap [ rsIDAlias ]
if ( exists == true ) {
return true
}
}
return false
}
aliasAlreadyExists := checkIfAliasAlreadyExists ( )
if ( aliasAlreadyExists == true ) {
// We already added this alias
continue
}
allUniqueRSIDsMap [ rsID ] = struct { } { }
}
// Map Structure: Genome Identifier -> PersonGenomeMonogenicDiseaseInfo
monogenicDiseaseInfoMap := make ( map [ [ 16 ] byte ] geneticAnalysis . PersonGenomeMonogenicDiseaseInfo )
for _ , genomeWithMetadataObject := range inputGenomesWithMetadataList {
genomeIdentifier := genomeWithMetadataObject . GenomeIdentifier
genomeMap := genomeWithMetadataObject . GenomeMap
// This stores all variant info for this genome
// Map Structure: Variant Identifier -> PersonGenomeMonogenicDiseaseVariantInfo
variantsInfoMap := make ( map [ [ 3 ] byte ] geneticAnalysis . PersonGenomeMonogenicDiseaseVariantInfo )
for _ , variantObject := range variantsList {
variantIdentifierHex := variantObject . VariantIdentifier
variantIdentifier , err := encoding . DecodeHexStringTo3ByteArray ( variantIdentifierHex )
if ( err != nil ) { return emptyDiseaseInfoObject , err }
variantRSID := variantObject . VariantRSID
2024-04-11 15:51:56 +02:00
basePairValueFound , base1Value , base2Value , locusIsPhased , err := getGenomeLocusBasePair ( genomeMap , variantRSID )
2024-06-02 10:43:39 +02:00
if ( err != nil ) { return emptyDiseaseInfoObject , err }
2024-04-11 15:51:56 +02:00
if ( basePairValueFound == false ) {
2024-06-02 10:43:39 +02:00
// This genome does not contain info for this variant
// We skip it
2024-04-11 15:51:56 +02:00
continue
}
2024-06-02 10:43:39 +02:00
// This genome has at least 1 variant
variantDefectiveBase := variantObject . DefectiveBase
2024-04-11 15:51:56 +02:00
getBaseIsVariantMutationBool := func ( inputBase string ) bool {
if ( inputBase == variantDefectiveBase ) {
return true
}
// Base could be mutated to a different unhealthy base
// That mutation could be a neutral/healthier change
// We only care about this specific variant
return false
}
base1IsDefective := getBaseIsVariantMutationBool ( base1Value )
base2IsDefective := getBaseIsVariantMutationBool ( base2Value )
2024-06-02 10:43:39 +02:00
newDiseaseVariantInfoObject := geneticAnalysis . PersonGenomeMonogenicDiseaseVariantInfo {
Base1HasVariant : base1IsDefective ,
Base2HasVariant : base2IsDefective ,
LocusIsPhased : locusIsPhased ,
}
2024-04-11 15:51:56 +02:00
2024-06-02 10:43:39 +02:00
variantsInfoMap [ variantIdentifier ] = newDiseaseVariantInfoObject
2024-04-11 15:51:56 +02:00
//TODO: Add LocusIsPhased to readGeneticAnalysis package
}
2024-06-02 10:43:39 +02:00
// We are done adding variant information for the genome
// Now we determine probability that user will pass a disease variant to offspring, and if the user has the disease
2024-04-11 15:51:56 +02:00
2024-06-02 10:43:39 +02:00
numberOfVariantsTested := len ( variantsInfoMap )
2024-04-11 15:51:56 +02:00
2024-06-02 10:43:39 +02:00
if ( numberOfVariantsTested == 0 ) {
// We don't know anything about this genome's disease risk for this disease
// We won't add any information to the map
continue
}
2024-04-11 15:51:56 +02:00
2024-06-02 10:43:39 +02:00
// This stores the number of loci that were tested
// Each locus can have multiple potential variants
numberOfLociTested := 0
2024-04-11 15:51:56 +02:00
2024-06-02 10:43:39 +02:00
// This stores the number of tested loci that are phased
// A higher number means that the results are more potentially more accurate
// It is only more accurate if multiple heterozygous variants on seperate loci exist.
numberOfPhasedLoci := 0
2024-04-11 15:51:56 +02:00
2024-06-02 10:43:39 +02:00
for rsID , _ := range allUniqueRSIDsMap {
2024-04-11 15:51:56 +02:00
2024-06-02 10:43:39 +02:00
locusValueExists , _ , _ , locusIsPhased , err := getGenomeLocusBasePair ( genomeMap , rsID )
if ( err != nil ) { return emptyDiseaseInfoObject , err }
if ( locusValueExists == false ) {
continue
2024-04-11 15:51:56 +02:00
}
2024-06-02 10:43:39 +02:00
numberOfLociTested += 1
if ( locusIsPhased == true ) {
numberOfPhasedLoci += 1
2024-04-11 15:51:56 +02:00
}
}
// Outputs:
2024-06-02 10:43:39 +02:00
// -bool: Person has disease
// -float64: Probability Person will pass a defect (variant) to offspring (0-1)
2024-04-11 15:51:56 +02:00
// -error
2024-06-02 10:43:39 +02:00
getPersonDiseaseInfo := func ( ) ( bool , float64 , error ) {
2024-04-11 15:51:56 +02:00
// These variables are used to count the number of defective variants that exist on each chromosome
numberOfVariants_Chromosome1 := 0
numberOfVariants_Chromosome2 := 0
numberOfVariants_UnknownChromosome := 0
2024-06-02 10:43:39 +02:00
// We use this map to keep track of how many mutations exist for each rsID
// This allows us to know if 2 different variant mutations exist for a single rsID
2024-04-11 15:51:56 +02:00
// For example, base1 is a different deleterious mutation than base2
// If this ever happens, we know that the user has the disease,
// because both copies of the gene locus are defective.
rsidMutationsMap := make ( map [ int64 ] int )
2024-06-02 10:43:39 +02:00
for variantIdentifier , variantInfoObject := range variantsInfoMap {
2024-04-11 15:51:56 +02:00
2024-06-02 10:43:39 +02:00
locusIsPhasedStatus := variantInfoObject . LocusIsPhased
2024-04-11 15:51:56 +02:00
2024-06-02 10:43:39 +02:00
base1HasVariant := variantInfoObject . Base1HasVariant
base2HasVariant := variantInfoObject . Base2HasVariant
2024-04-11 15:51:56 +02:00
2024-06-02 10:43:39 +02:00
if ( base1HasVariant == false && base2HasVariant == false ) {
2024-04-11 15:51:56 +02:00
// Neither chromosome contains the variant mutation.
continue
}
2024-06-02 10:43:39 +02:00
if ( base1HasVariant == true && base2HasVariant == true ) {
2024-04-11 15:51:56 +02:00
// Both chromosomes contain the same variant mutation.
// Person has the disease.
// Person will definitely pass disease variant to offspring.
2024-06-02 10:43:39 +02:00
return true , 1 , nil
2024-04-11 15:51:56 +02:00
}
// We know that this variant exists on 1 of the bases, but not both.
variantRSIDsList , exists := variantRSIDsMap [ variantIdentifier ]
if ( exists == false ) {
2024-06-02 10:43:39 +02:00
return false , 0 , errors . New ( "variantRSIDsMap missing variantIdentifier." )
2024-04-11 15:51:56 +02:00
}
2024-06-02 10:43:39 +02:00
for _ , rsID := range variantRSIDsList {
rsidMutationsMap [ rsID ] += 1
2024-04-11 15:51:56 +02:00
}
2024-06-02 10:43:39 +02:00
if ( locusIsPhasedStatus == true ) {
2024-04-11 15:51:56 +02:00
2024-06-02 10:43:39 +02:00
if ( base1HasVariant == true ) {
2024-04-11 15:51:56 +02:00
numberOfVariants_Chromosome1 += 1
}
2024-06-02 10:43:39 +02:00
if ( base2HasVariant == true ) {
2024-04-11 15:51:56 +02:00
numberOfVariants_Chromosome2 += 1
}
} else {
2024-06-02 10:43:39 +02:00
if ( base1HasVariant == true || base2HasVariant == true ) {
2024-04-11 15:51:56 +02:00
numberOfVariants_UnknownChromosome += 1
}
}
}
totalNumberOfVariants := numberOfVariants_Chromosome1 + numberOfVariants_Chromosome2 + numberOfVariants_UnknownChromosome
if ( totalNumberOfVariants == 0 ) {
// Person does not have any disease variants.
// They do not have the disease, and have no chance of passing a disease variant
2024-06-02 10:43:39 +02:00
return false , 0 , nil
2024-04-11 15:51:56 +02:00
}
// Now we check to see if there are any loci which have 2 different variants, one for each base
for _ , numberOfMutations := range rsidMutationsMap {
if ( numberOfMutations >= 2 ) {
// Person has 2 mutations on the same location
// They must have the disease, and will definitely pass a variant to their offspring
2024-06-02 10:43:39 +02:00
return true , 1 , nil
2024-04-11 15:51:56 +02:00
}
}
// At this point, we know that there are no homozygous variant mutations
// All variant mutations are heterozygous, meaning the other chromosome base is healthy
// Probability is expressed as a float between 0 - 1
2024-06-02 10:43:39 +02:00
getPersonHasDiseaseBool := func ( ) bool {
2024-04-11 15:51:56 +02:00
if ( dominantOrRecessive == "Dominant" ) {
// Only 1 variant is needed for the person to have the disease
// We know they have at least 1 variant
2024-06-02 10:43:39 +02:00
return true
2024-04-11 15:51:56 +02:00
}
// dominantOrRecessive == "Recessive"
if ( totalNumberOfVariants == 1 ) {
// There is only 1 variant in total.
// This single variant cannot exist on both chromosomes.
// The person does not have the disease
2024-06-02 10:43:39 +02:00
return false
2024-04-11 15:51:56 +02:00
}
if ( numberOfVariants_Chromosome1 >= 1 && numberOfVariants_Chromosome2 >= 1 ) {
// We know there is at least 1 variant mutation on each chromosome
// Therefore, the person has the disease
2024-06-02 10:43:39 +02:00
return true
2024-04-11 15:51:56 +02:00
}
if ( numberOfVariants_UnknownChromosome == 0 ) {
// We know that variants do not exist on both chromosomes, only on 1.
// Thus, the person does not have the disease
2024-06-02 10:43:39 +02:00
return false
2024-04-11 15:51:56 +02:00
}
if ( numberOfVariants_Chromosome1 == 0 && numberOfVariants_Chromosome2 == 0 ) {
// All variants have an unknown phase, and we know there are multiple of them.
2024-06-02 10:43:39 +02:00
// The person does not have the disease if all mutations are on the same chromosome
// The person does have the disease if at least 1 mutation exists on each chromosome
// Either way, we don't know enough to say if the person has the disease
// We will report that they do not, because their genome does not conclusively say that they do
// This is why phased genomes are useful and provide a more accurate reading
// TODO: Explain this to the user in the GUI
// We must explain that unphased genomes will not detect disease sometimes
2024-04-11 15:51:56 +02:00
2024-06-02 10:43:39 +02:00
return false
2024-04-11 15:51:56 +02:00
}
// We know that there is at least 1 variant whose phase is known
// We know that there are no variants whose phase is known which exist on both chromosomes
// We know there are at least some variants whose phase is unknown
2024-06-02 10:43:39 +02:00
// The person has the disease if the unknown-phase variants exist on the same chromosome as the ones which we know exist do
// This is the same as the last if statement, we report false even though they may actually have the disease
2024-04-11 15:51:56 +02:00
2024-06-02 10:43:39 +02:00
return false
2024-04-11 15:51:56 +02:00
}
2024-06-02 10:43:39 +02:00
personHasDiseaseBool := getPersonHasDiseaseBool ( )
2024-04-11 15:51:56 +02:00
// We know all variants are heterozygous
// Probability person will not pass any of n variants to their offspring: 1/(2^n)
// Probability person will pass at least 1 of n variants to their offspring: 1 - (1/(2^n))
probabilityPersonWillPassAnyVariant := 1 - ( 1 / ( math . Pow ( 2 , float64 ( totalNumberOfVariants ) ) ) )
2024-06-02 10:43:39 +02:00
return personHasDiseaseBool , probabilityPersonWillPassAnyVariant , nil
2024-04-11 15:51:56 +02:00
}
2024-06-02 10:43:39 +02:00
personHasDisease , probabilityPersonWillPassAnyVariant , err := getPersonDiseaseInfo ( )
if ( err != nil ) { return emptyDiseaseInfoObject , err }
2024-04-11 15:51:56 +02:00
2024-06-02 10:43:39 +02:00
percentageProbabilityPersonWillPassADiseaseVariant := int ( probabilityPersonWillPassAnyVariant * 100 )
2024-04-11 15:51:56 +02:00
2024-06-02 10:43:39 +02:00
diseaseAnalysisObject := geneticAnalysis . PersonGenomeMonogenicDiseaseInfo {
PersonHasDisease : personHasDisease ,
NumberOfVariantsTested : numberOfVariantsTested ,
NumberOfLociTested : numberOfLociTested ,
NumberOfPhasedLoci : numberOfPhasedLoci ,
ProbabilityOfPassingADiseaseVariant : percentageProbabilityPersonWillPassADiseaseVariant ,
VariantsInfoMap : variantsInfoMap ,
}
2024-04-11 15:51:56 +02:00
2024-06-02 10:43:39 +02:00
monogenicDiseaseInfoMap [ genomeIdentifier ] = diseaseAnalysisObject
2024-04-11 15:51:56 +02:00
}
2024-06-02 10:43:39 +02:00
personMonogenicDiseaseInfoObject := geneticAnalysis . PersonMonogenicDiseaseInfo {
MonogenicDiseaseInfoMap : monogenicDiseaseInfoMap ,
}
if ( len ( monogenicDiseaseInfoMap ) <= 1 ) {
// We do not need to check for conflicts, there is only <=1 genome with disease information
2024-04-11 15:51:56 +02:00
// Nothing left to do. Analysis is complete.
2024-06-02 10:43:39 +02:00
return personMonogenicDiseaseInfoObject , nil
2024-04-11 15:51:56 +02:00
}
// We check for conflicts
getConflictExistsBool := func ( ) ( bool , error ) {
2024-06-02 10:43:39 +02:00
firstItemReached := false
2024-04-11 15:51:56 +02:00
2024-06-02 10:43:39 +02:00
personHasDisease := false
probabilityOfPassingAVariant := 0
2024-04-11 15:51:56 +02:00
2024-06-02 10:43:39 +02:00
for _ , currentGenomeDiseaseAnalysisObject := range monogenicDiseaseInfoMap {
2024-04-11 15:51:56 +02:00
2024-06-02 10:43:39 +02:00
currentGenomePersonHasDisease := currentGenomeDiseaseAnalysisObject . PersonHasDisease
currentGenomeProbabilityOfPassingAVariant := currentGenomeDiseaseAnalysisObject . ProbabilityOfPassingADiseaseVariant
2024-04-11 15:51:56 +02:00
2024-06-02 10:43:39 +02:00
if ( firstItemReached == false ) {
personHasDisease = currentGenomePersonHasDisease
probabilityOfPassingAVariant = currentGenomeProbabilityOfPassingAVariant
firstItemReached = true
2024-04-11 15:51:56 +02:00
continue
}
2024-06-02 10:43:39 +02:00
if ( currentGenomePersonHasDisease != personHasDisease ) {
2024-04-11 15:51:56 +02:00
return true , nil
}
2024-06-02 10:43:39 +02:00
if ( currentGenomeProbabilityOfPassingAVariant != probabilityOfPassingAVariant ) {
2024-04-11 15:51:56 +02:00
return true , nil
}
}
// Now we test variants for conflicts
2024-06-02 10:43:39 +02:00
// We are only doing this to see if there are variants which one genome has and another doesn't
// For example, the analysis results say that you have a 50% chance of passing a variant for both genomes, but
// they have detected a different variant for each genome.
// This means that your real risk of passing a variant may actually be higher, and you are more likely to have the disease too
2024-04-11 15:51:56 +02:00
2024-06-02 10:43:39 +02:00
for variantIdentifier , _ := range variantRSIDsMap {
2024-04-11 15:51:56 +02:00
2024-06-02 10:43:39 +02:00
// Each variant base pair is either true/false, true/true, false/false, false/true
2024-04-11 15:51:56 +02:00
2024-06-02 10:43:39 +02:00
// Two different genomes have true/false and false/true, it will not count as a conflict
// If the locus is unphased, then there is no difference between true/false and false/true
2024-04-11 15:51:56 +02:00
// If the locus is phased, then this flip is only meaningful if it effects the probability of disease/passing a variant
// We already checked those probabilities for conflicts earlier
// Therefore, any flip is not considered a conflict
2024-06-02 10:43:39 +02:00
// We only care about conflicts where 1 genome says you have a variant and the other says you don't, or
// one says you have only 1 mutation and the other says you have 2 at that location
2024-04-11 15:51:56 +02:00
2024-06-02 10:43:39 +02:00
firstItemReached := false
2024-04-11 15:51:56 +02:00
2024-06-02 10:43:39 +02:00
base1HasVariant := false
base2HasVariant := false
2024-04-11 15:51:56 +02:00
2024-06-02 10:43:39 +02:00
for _ , currentGenomeDiseaseAnalysisObject := range monogenicDiseaseInfoMap {
2024-04-11 15:51:56 +02:00
2024-06-02 10:43:39 +02:00
// Outputs:
// -bool: Bases are known
// -bool: Base1 Has Variant
// -bool: Base2 Has Variant
getGenomeBasesInfo := func ( ) ( bool , bool , bool ) {
2024-04-11 15:51:56 +02:00
2024-06-02 10:43:39 +02:00
variantsInfoMap := currentGenomeDiseaseAnalysisObject . VariantsInfoMap
2024-04-11 15:51:56 +02:00
2024-06-02 10:43:39 +02:00
variantInfoObject , exists := variantsInfoMap [ variantIdentifier ]
if ( exists == false ) {
return false , false , false
2024-04-11 15:51:56 +02:00
}
2024-06-02 10:43:39 +02:00
currentBase1HasVariant := variantInfoObject . Base1HasVariant
currentBase2HasVariant := variantInfoObject . Base2HasVariant
return true , currentBase1HasVariant , currentBase2HasVariant
}
basesAreKnown , currentBase1HasVariant , currentBase2HasVariant := getGenomeBasesInfo ( )
if ( basesAreKnown == false ) {
if ( firstItemReached == true ) {
// A previous genome has information for this variant, and the current one does not
return true , nil
2024-04-11 15:51:56 +02:00
}
2024-06-02 10:43:39 +02:00
continue
}
if ( firstItemReached == false ) {
base1HasVariant = currentBase1HasVariant
base2HasVariant = currentBase2HasVariant
firstItemReached = true
continue
}
if ( base1HasVariant == currentBase1HasVariant && base2HasVariant == currentBase2HasVariant ) {
// No conflict exists
continue
}
if ( base1HasVariant == currentBase2HasVariant && base2HasVariant == currentBase1HasVariant ) {
// We don't count this as a conflict
continue
2024-04-11 15:51:56 +02:00
}
2024-06-02 10:43:39 +02:00
// A conflict exists
return true , nil
2024-04-11 15:51:56 +02:00
}
}
return false , nil
}
conflictExists , err := getConflictExistsBool ( )
2024-06-02 10:43:39 +02:00
if ( err != nil ) { return emptyDiseaseInfoObject , err }
2024-04-11 15:51:56 +02:00
2024-06-02 10:43:39 +02:00
personMonogenicDiseaseInfoObject . ConflictExists = conflictExists
2024-04-11 15:51:56 +02:00
2024-06-02 10:43:39 +02:00
return personMonogenicDiseaseInfoObject , nil
2024-04-11 15:51:56 +02:00
}
//Outputs:
2024-06-02 10:43:39 +02:00
// -geneticAnalysis.PersonPolygenicDiseaseInfo
2024-04-11 15:51:56 +02:00
// -error
2024-06-02 10:43:39 +02:00
func getPersonPolygenicDiseaseAnalysis ( inputGenomesWithMetadataList [ ] prepareRawGenomes . GenomeWithMetadata , diseaseObject polygenicDiseases . PolygenicDisease ) ( geneticAnalysis . PersonPolygenicDiseaseInfo , error ) {
// We use this when returning errors
emptyDiseaseInfoObject := geneticAnalysis . PersonPolygenicDiseaseInfo { }
2024-04-11 15:51:56 +02:00
diseaseLociList := diseaseObject . LociList
2024-06-02 10:43:39 +02:00
// This map stores the polygenic disease for each of the person's genomes
// Map Structure: Genome Identifier -> PersonGenomePolygenicDiseaseInfo
personPolygenicDiseaseInfoMap := make ( map [ [ 16 ] byte ] geneticAnalysis . PersonGenomePolygenicDiseaseInfo )
2024-04-11 15:51:56 +02:00
2024-06-02 10:43:39 +02:00
// We construct polygenic disease probability info for each genome
2024-04-11 15:51:56 +02:00
2024-06-02 10:43:39 +02:00
for _ , genomeWithMetadataObject := range inputGenomesWithMetadataList {
2024-04-11 15:51:56 +02:00
2024-06-02 10:43:39 +02:00
genomeIdentifier := genomeWithMetadataObject . GenomeIdentifier
genomeMap := genomeWithMetadataObject . GenomeMap
2024-04-11 15:51:56 +02:00
2024-06-02 10:43:39 +02:00
// Map Structure: Locus Identifier -> PersonGenomePolygenicDiseaseLocusInfo
genomeLociInfoMap := make ( map [ [ 3 ] byte ] geneticAnalysis . PersonGenomePolygenicDiseaseLocusInfo )
2024-04-11 15:51:56 +02:00
2024-06-02 10:43:39 +02:00
minimumPossibleRiskWeightSum := 0
maximumPossibleRiskWeightSum := 0
2024-04-11 15:51:56 +02:00
2024-06-02 10:43:39 +02:00
summedDiseaseRiskWeight := 0
2024-04-11 15:51:56 +02:00
2024-06-02 10:43:39 +02:00
for _ , locusObject := range diseaseLociList {
locusIdentifierHex := locusObject . LocusIdentifier
2024-04-11 15:51:56 +02:00
2024-06-02 10:43:39 +02:00
locusIdentifier , err := encoding . DecodeHexStringTo3ByteArray ( locusIdentifierHex )
if ( err != nil ) { return emptyDiseaseInfoObject , err }
locusRSID := locusObject . LocusRSID
locusRiskWeightsMap := locusObject . RiskWeightsMap
locusOddsRatiosMap := locusObject . OddsRatiosMap
locusMinimumWeight := locusObject . MinimumRiskWeight
locusMaximumWeight := locusObject . MaximumRiskWeight
basePairValueFound , locusBase1Value , locusBase2Value , _ , err := getGenomeLocusBasePair ( genomeMap , locusRSID )
if ( err != nil ) { return emptyDiseaseInfoObject , err }
if ( basePairValueFound == false ) {
continue
}
2024-04-11 15:51:56 +02:00
//Outputs:
// -int: Genome disease locus risk weight
// -bool: Genome disease locus odds ratio known
// -float64: Genome disease locus odds ratio
// -error
2024-06-02 10:43:39 +02:00
getGenomeDiseaseLocusRiskInfo := func ( ) ( int , bool , float64 , error ) {
2024-04-11 15:51:56 +02:00
2024-06-02 10:43:39 +02:00
locusBasePairJoined := locusBase1Value + ";" + locusBase2Value
2024-04-11 15:51:56 +02:00
2024-06-02 10:43:39 +02:00
riskWeight , exists := locusRiskWeightsMap [ locusBasePairJoined ]
2024-04-11 15:51:56 +02:00
if ( exists == false ) {
// This is an unknown base combination
// We will treat it as a 0 risk weight
2024-06-02 10:43:39 +02:00
return 0 , true , 1 , nil
2024-04-11 15:51:56 +02:00
}
if ( riskWeight == 0 ) {
2024-06-02 10:43:39 +02:00
return 0 , true , 1 , nil
2024-04-11 15:51:56 +02:00
}
2024-06-02 10:43:39 +02:00
oddsRatio , exists := locusOddsRatiosMap [ locusBasePairJoined ]
2024-04-11 15:51:56 +02:00
if ( exists == false ) {
2024-06-02 10:43:39 +02:00
return riskWeight , false , 0 , nil
2024-04-11 15:51:56 +02:00
}
2024-06-02 10:43:39 +02:00
return riskWeight , true , oddsRatio , nil
2024-04-11 15:51:56 +02:00
}
2024-06-02 10:43:39 +02:00
locusRiskWeight , locusOddsRatioIsKnown , locusOddsRatio , err := getGenomeDiseaseLocusRiskInfo ( )
if ( err != nil ) { return emptyDiseaseInfoObject , err }
2024-04-11 15:51:56 +02:00
2024-06-02 10:43:39 +02:00
newLocusInfoObject := geneticAnalysis . PersonGenomePolygenicDiseaseLocusInfo {
LocusBase1 : locusBase1Value ,
LocusBase2 : locusBase2Value ,
RiskWeight : locusRiskWeight ,
OddsRatioIsKnown : locusOddsRatioIsKnown ,
}
2024-04-11 15:51:56 +02:00
2024-06-02 10:43:39 +02:00
if ( locusOddsRatioIsKnown == true ) {
newLocusInfoObject . OddsRatio = locusOddsRatio
}
2024-04-11 15:51:56 +02:00
2024-06-02 10:43:39 +02:00
genomeLociInfoMap [ locusIdentifier ] = newLocusInfoObject
2024-04-11 15:51:56 +02:00
2024-06-02 10:43:39 +02:00
minimumPossibleRiskWeightSum += locusMinimumWeight
maximumPossibleRiskWeightSum += locusMaximumWeight
2024-04-11 15:51:56 +02:00
2024-06-02 10:43:39 +02:00
summedDiseaseRiskWeight += locusRiskWeight
}
2024-04-11 15:51:56 +02:00
2024-06-02 10:43:39 +02:00
numberOfLociTested := len ( genomeLociInfoMap )
2024-04-11 15:51:56 +02:00
2024-06-02 10:43:39 +02:00
if ( numberOfLociTested == 0 ) {
// We have no information about this disease for this genome
continue
}
2024-04-11 15:51:56 +02:00
2024-06-02 10:43:39 +02:00
diseaseRiskScore , err := helpers . ScaleNumberProportionally ( true , summedDiseaseRiskWeight , minimumPossibleRiskWeightSum , maximumPossibleRiskWeightSum , 0 , 10 )
if ( err != nil ) { return emptyDiseaseInfoObject , err }
2024-04-11 15:51:56 +02:00
2024-06-02 10:43:39 +02:00
newDiseaseInfoObject := geneticAnalysis . PersonGenomePolygenicDiseaseInfo {
NumberOfLociTested : numberOfLociTested ,
RiskScore : diseaseRiskScore ,
LociInfoMap : genomeLociInfoMap ,
}
2024-04-11 15:51:56 +02:00
2024-06-02 10:43:39 +02:00
personPolygenicDiseaseInfoMap [ genomeIdentifier ] = newDiseaseInfoObject
}
2024-04-11 15:51:56 +02:00
2024-06-02 10:43:39 +02:00
newPersonPolygenicDiseaseInfoObject := geneticAnalysis . PersonPolygenicDiseaseInfo {
PolygenicDiseaseInfoMap : personPolygenicDiseaseInfoMap ,
}
2024-04-11 15:51:56 +02:00
2024-06-02 10:43:39 +02:00
if ( len ( personPolygenicDiseaseInfoMap ) <= 1 ) {
// We do not need to check for conflicts, there is only <=1 genome with disease information
// Nothing left to do. Analysis is complete.
return newPersonPolygenicDiseaseInfoObject , nil
}
2024-04-11 15:51:56 +02:00
2024-06-02 10:43:39 +02:00
// We check for conflicts between the different genome's results
2024-04-11 15:51:56 +02:00
2024-06-02 10:43:39 +02:00
getConflictExistsBool := func ( ) ( bool , error ) {
2024-04-11 15:51:56 +02:00
2024-06-02 10:43:39 +02:00
// First we check to see if any of the genomes have different risk scores or NumberOfLociTested
2024-04-11 15:51:56 +02:00
2024-06-02 10:43:39 +02:00
genomeRiskScore := 0
genomeNumberOfLociTested := 0
2024-04-11 15:51:56 +02:00
2024-06-02 10:43:39 +02:00
firstItemReached := false
2024-04-11 15:51:56 +02:00
2024-06-02 10:43:39 +02:00
for _ , personGenomeDiseaseInfoObject := range personPolygenicDiseaseInfoMap {
2024-04-11 15:51:56 +02:00
2024-06-02 10:43:39 +02:00
currentGenomeRiskScore := personGenomeDiseaseInfoObject . RiskScore
currentGenomeNumberOfLociTested := personGenomeDiseaseInfoObject . NumberOfLociTested
2024-04-11 15:51:56 +02:00
2024-06-02 10:43:39 +02:00
if ( firstItemReached == false ) {
genomeRiskScore = currentGenomeRiskScore
genomeNumberOfLociTested = currentGenomeNumberOfLociTested
firstItemReached = true
continue
}
2024-04-11 15:51:56 +02:00
2024-06-02 10:43:39 +02:00
if ( genomeRiskScore != currentGenomeRiskScore ) {
return true , nil
}
if ( genomeNumberOfLociTested != currentGenomeNumberOfLociTested ) {
return true , nil
}
2024-04-11 15:51:56 +02:00
}
2024-06-02 10:43:39 +02:00
// Now we check for conflicts between the different locus values
// We consider a conflict any time the same locus has different weights/odds ratios
// We don't care if the loci have different base pair values, so long as those base pairs have the same risk weights/odds ratios
2024-04-11 15:51:56 +02:00
2024-06-02 10:43:39 +02:00
for _ , locusObject := range diseaseLociList {
2024-04-11 15:51:56 +02:00
2024-06-02 10:43:39 +02:00
locusIdentifierHex := locusObject . LocusIdentifier
2024-04-11 15:51:56 +02:00
2024-06-02 10:43:39 +02:00
locusIdentifier , err := encoding . DecodeHexStringTo3ByteArray ( locusIdentifierHex )
if ( err != nil ) { return false , err }
2024-04-11 15:51:56 +02:00
2024-06-02 10:43:39 +02:00
locusRiskWeight := 0
locusOddsRatio := float64 ( 0 )
2024-04-11 15:51:56 +02:00
2024-06-02 10:43:39 +02:00
firstItemReached := false
2024-04-11 15:51:56 +02:00
2024-06-02 10:43:39 +02:00
for _ , personGenomeDiseaseInfoObject := range personPolygenicDiseaseInfoMap {
2024-04-11 15:51:56 +02:00
2024-06-02 10:43:39 +02:00
genomeLociInfoMap := personGenomeDiseaseInfoObject . LociInfoMap
2024-04-11 15:51:56 +02:00
2024-06-02 10:43:39 +02:00
genomeLocusObject , exists := genomeLociInfoMap [ locusIdentifier ]
2024-04-11 15:51:56 +02:00
if ( exists == false ) {
2024-06-02 10:43:39 +02:00
if ( firstItemReached == true ) {
// A previous genome has information for this locus, and the current one does not
return true , nil
}
continue
2024-04-11 15:51:56 +02:00
}
2024-06-02 10:43:39 +02:00
genomeLocusRiskWeight := genomeLocusObject . RiskWeight
genomeLocusOddsRatio := genomeLocusObject . OddsRatio
2024-04-11 15:51:56 +02:00
2024-06-02 10:43:39 +02:00
if ( firstItemReached == false ) {
locusRiskWeight = genomeLocusRiskWeight
locusOddsRatio = genomeLocusOddsRatio
firstItemReached = true
2024-04-11 15:51:56 +02:00
continue
}
2024-06-02 10:43:39 +02:00
if ( locusRiskWeight == genomeLocusRiskWeight && locusOddsRatio == genomeLocusOddsRatio ) {
// No conflict exists for this locus on the genomes we have already checked
continue
2024-04-11 15:51:56 +02:00
}
2024-06-02 10:43:39 +02:00
// Conflict exists
return true , nil
2024-04-11 15:51:56 +02:00
}
}
return false , nil
}
conflictExists , err := getConflictExistsBool ( )
2024-06-02 10:43:39 +02:00
if ( err != nil ) { return emptyDiseaseInfoObject , err }
2024-04-11 15:51:56 +02:00
2024-06-02 10:43:39 +02:00
newPersonPolygenicDiseaseInfoObject . ConflictExists = conflictExists
2024-04-11 15:51:56 +02:00
2024-06-02 10:43:39 +02:00
return newPersonPolygenicDiseaseInfoObject , nil
2024-04-11 15:51:56 +02:00
}
//Outputs:
2024-06-02 10:43:39 +02:00
// -geneticAnalysis.PersonTraitInfo: Trait analysis object
2024-04-11 15:51:56 +02:00
// -error
2024-06-02 10:43:39 +02:00
func getPersonTraitAnalysis ( inputGenomesWithMetadataList [ ] prepareRawGenomes . GenomeWithMetadata , traitObject traits . Trait ) ( geneticAnalysis . PersonTraitInfo , error ) {
// We use this when returning errors
emptyPersonTraitInfo := geneticAnalysis . PersonTraitInfo { }
2024-04-11 15:51:56 +02:00
traitLociList := traitObject . LociList
traitRulesList := traitObject . RulesList
2024-06-02 10:43:39 +02:00
// Map Structure: Genome Identifier -> PersonGenomeTraitInfo
newPersonTraitInfoMap := make ( map [ [ 16 ] byte ] geneticAnalysis . PersonGenomeTraitInfo )
2024-04-11 15:51:56 +02:00
for _ , genomeWithMetadataObject := range inputGenomesWithMetadataList {
genomeIdentifier := genomeWithMetadataObject . GenomeIdentifier
genomeMap := genomeWithMetadataObject . GenomeMap
2024-06-02 10:43:39 +02:00
// This map contains the locus values for the genome
// If an locus's entry doesn't exist, its value is unknown
// Map Structure: Locus rsID -> Locus Value
genomeLocusValuesMap := make ( map [ int64 ] locusValue . LocusValue )
2024-04-11 15:51:56 +02:00
2024-06-02 10:43:39 +02:00
for _ , locusRSID := range traitLociList {
2024-04-11 15:51:56 +02:00
locusBasePairKnown , locusBase1 , locusBase2 , locusIsPhased , err := getGenomeLocusBasePair ( genomeMap , locusRSID )
2024-06-02 10:43:39 +02:00
if ( err != nil ) { return emptyPersonTraitInfo , err }
2024-04-11 15:51:56 +02:00
if ( locusBasePairKnown == false ) {
2024-06-02 10:43:39 +02:00
continue
}
2024-04-11 15:51:56 +02:00
2024-06-02 10:43:39 +02:00
newLocusValue := locusValue . LocusValue {
LocusIsPhased : locusIsPhased ,
Base1Value : locusBase1 ,
Base2Value : locusBase2 ,
2024-04-11 15:51:56 +02:00
}
2024-06-02 10:43:39 +02:00
genomeLocusValuesMap [ locusRSID ] = newLocusValue
}
2024-04-11 15:51:56 +02:00
2024-06-02 10:43:39 +02:00
// This map contains the trait outcome scores for the genome
// Map Structure: Outcome Name -> Score
// Example: "Intolerant" -> 5
traitOutcomeScoresMap := make ( map [ string ] int )
2024-04-11 15:51:56 +02:00
2024-06-02 10:43:39 +02:00
// Map Structure: Rule Identifier -> Genome Passes rule (true if the genome passes the rule)
personPassesRulesMap := make ( map [ [ 3 ] byte ] bool )
2024-04-11 15:51:56 +02:00
2024-06-02 10:43:39 +02:00
if ( len ( traitRulesList ) != 0 ) {
2024-04-11 15:51:56 +02:00
2024-06-02 10:43:39 +02:00
// At least 1 rule exists for this trait
2024-04-11 15:51:56 +02:00
2024-06-02 10:43:39 +02:00
for _ , ruleObject := range traitRulesList {
2024-04-11 15:51:56 +02:00
2024-06-02 10:43:39 +02:00
ruleIdentifierHex := ruleObject . RuleIdentifier
2024-04-11 15:51:56 +02:00
2024-06-02 10:43:39 +02:00
ruleIdentifier , err := encoding . DecodeHexStringTo3ByteArray ( ruleIdentifierHex )
if ( err != nil ) { return emptyPersonTraitInfo , err }
2024-04-11 15:51:56 +02:00
2024-06-02 10:43:39 +02:00
ruleLociList := ruleObject . LociList
2024-04-11 15:51:56 +02:00
2024-06-02 10:43:39 +02:00
// Outputs:
// -bool: Genome passes rule is known
// -bool: Genome passes rule
// -error
getGenomePassesRuleBool := func ( ) ( bool , bool , error ) {
2024-04-11 15:51:56 +02:00
2024-06-02 10:43:39 +02:00
// We check to see if genome passes all rule loci
// We consider a rule Known if the genome either passes all loci, or fails to pass 1 locus
// We consider a rule Unknown if any loci are unknown, and all loci which are known pass the rule
2024-04-11 15:51:56 +02:00
2024-06-02 10:43:39 +02:00
anyLocusIsUnknown := false
2024-04-11 15:51:56 +02:00
2024-06-02 10:43:39 +02:00
for _ , locusObject := range ruleLociList {
2024-04-11 15:51:56 +02:00
2024-06-02 10:43:39 +02:00
locusRSID := locusObject . LocusRSID
2024-04-11 15:51:56 +02:00
2024-06-02 10:43:39 +02:00
locusBasePairKnown , locusBase1 , locusBase2 , _ , err := getGenomeLocusBasePair ( genomeMap , locusRSID )
if ( err != nil ) { return false , false , err }
if ( locusBasePairKnown == false ) {
anyLocusIsUnknown = true
continue
}
2024-04-11 15:51:56 +02:00
2024-06-02 10:43:39 +02:00
locusBasePairJoined := locusBase1 + ";" + locusBase2
2024-04-11 15:51:56 +02:00
2024-06-02 10:43:39 +02:00
locusBasePairsList := locusObject . BasePairsList
2024-04-11 15:51:56 +02:00
2024-06-02 10:43:39 +02:00
genomePassesRuleLocus := slices . Contains ( locusBasePairsList , locusBasePairJoined )
if ( genomePassesRuleLocus == false ) {
// The genome has failed to pass a single rule locus, thus, the rule is not passed
return true , false , nil
}
2024-04-11 15:51:56 +02:00
}
2024-06-02 10:43:39 +02:00
if ( anyLocusIsUnknown == true ) {
// The rule is not passed, but it's status is unknown
// There were no rules which were known not to pass
return false , false , nil
2024-04-11 15:51:56 +02:00
}
2024-06-02 10:43:39 +02:00
// All rules were passed
2024-04-11 15:51:56 +02:00
2024-06-02 10:43:39 +02:00
return true , true , nil
2024-04-11 15:51:56 +02:00
}
2024-06-02 10:43:39 +02:00
genomePassesRuleIsKnown , genomePassesRule , err := getGenomePassesRuleBool ( )
if ( err != nil ) { return emptyPersonTraitInfo , err }
if ( genomePassesRuleIsKnown == false ) {
2024-04-11 15:51:56 +02:00
continue
}
2024-06-02 10:43:39 +02:00
personPassesRulesMap [ ruleIdentifier ] = genomePassesRule
2024-04-11 15:51:56 +02:00
2024-06-02 10:43:39 +02:00
// The rule has been passed by this genome
// We add the outcome points for the rule to the traitOutcomeScoresMap
2024-04-11 15:51:56 +02:00
ruleOutcomePointsMap := ruleObject . OutcomePointsMap
for traitOutcome , pointsChange := range ruleOutcomePointsMap {
traitOutcomeScoresMap [ traitOutcome ] += pointsChange
}
}
2024-06-02 10:43:39 +02:00
}
2024-04-11 15:51:56 +02:00
2024-06-02 10:43:39 +02:00
traitOutcomesList := traitObject . OutcomesList
2024-04-11 15:51:56 +02:00
2024-06-02 10:43:39 +02:00
// We add all outcomes for which there were no points
2024-04-11 15:51:56 +02:00
2024-06-02 10:43:39 +02:00
for _ , traitOutcome := range traitOutcomesList {
2024-04-11 15:51:56 +02:00
2024-06-02 10:43:39 +02:00
_ , exists := traitOutcomeScoresMap [ traitOutcome ]
if ( exists == false ) {
traitOutcomeScoresMap [ traitOutcome ] = 0
2024-04-11 15:51:56 +02:00
}
2024-06-02 10:43:39 +02:00
}
2024-04-11 15:51:56 +02:00
2024-06-02 10:43:39 +02:00
numberOfRulesTested := len ( personPassesRulesMap )
2024-04-11 15:51:56 +02:00
2024-06-02 10:43:39 +02:00
newPersonGenomeTraitInfo := geneticAnalysis . PersonGenomeTraitInfo {
NumberOfRulesTested : numberOfRulesTested ,
LocusValuesMap : genomeLocusValuesMap ,
OutcomeScoresMap : traitOutcomeScoresMap ,
GenomePassesRulesMap : personPassesRulesMap ,
2024-04-11 15:51:56 +02:00
}
2024-06-02 10:43:39 +02:00
newPersonTraitInfoMap [ genomeIdentifier ] = newPersonGenomeTraitInfo
}
2024-04-11 15:51:56 +02:00
2024-06-02 10:43:39 +02:00
newPersonTraitInfoObject := geneticAnalysis . PersonTraitInfo {
TraitInfoMap : newPersonTraitInfoMap ,
2024-04-11 15:51:56 +02:00
}
2024-06-02 10:43:39 +02:00
if ( len ( newPersonTraitInfoMap ) <= 1 ) {
// We do not need to check for conflicts, there is only <=1 genome with trait information
2024-04-11 15:51:56 +02:00
// Nothing left to do. Analysis is complete.
2024-06-02 10:43:39 +02:00
return newPersonTraitInfoObject , nil
2024-04-11 15:51:56 +02:00
}
// We check for conflicts
getConflictExistsBool := func ( ) ( bool , error ) {
//TODO: Check for locus value conflicts once locus values are used in neural network prediction.
2024-06-02 10:43:39 +02:00
if ( len ( traitRulesList ) == 0 ) {
return false , nil
}
2024-04-11 15:51:56 +02:00
2024-06-02 10:43:39 +02:00
// We check to see if the outcome scores are the same for all genomes
// We also check each rule result
2024-04-11 15:51:56 +02:00
2024-06-02 10:43:39 +02:00
firstItemReached := false
2024-04-11 15:51:56 +02:00
2024-06-02 10:43:39 +02:00
outcomeScoresMap := make ( map [ string ] int )
passesRulesMap := make ( map [ [ 3 ] byte ] bool )
2024-04-11 15:51:56 +02:00
2024-06-02 10:43:39 +02:00
for _ , genomeTraitInfoObject := range newPersonTraitInfoMap {
2024-04-11 15:51:56 +02:00
2024-06-02 10:43:39 +02:00
currentGenomeOutcomeScoresMap := genomeTraitInfoObject . OutcomeScoresMap
currentGenomePassesRulesMap := genomeTraitInfoObject . GenomePassesRulesMap
2024-04-11 15:51:56 +02:00
2024-06-02 10:43:39 +02:00
if ( firstItemReached == false ) {
outcomeScoresMap = currentGenomeOutcomeScoresMap
passesRulesMap = currentGenomePassesRulesMap
firstItemReached = true
continue
}
areEqual := maps . Equal ( currentGenomeOutcomeScoresMap , outcomeScoresMap )
if ( areEqual == false ) {
// A conflict exists
return true , nil
}
areEqual = maps . Equal ( currentGenomePassesRulesMap , passesRulesMap )
if ( areEqual == false ) {
// A conflict exists
return true , nil
2024-04-11 15:51:56 +02:00
}
}
return false , nil
}
conflictExists , err := getConflictExistsBool ( )
2024-06-02 10:43:39 +02:00
if ( err != nil ) { return emptyPersonTraitInfo , err }
2024-04-11 15:51:56 +02:00
2024-06-02 10:43:39 +02:00
newPersonTraitInfoObject . ConflictExists = conflictExists
2024-04-11 15:51:56 +02:00
2024-06-02 10:43:39 +02:00
return newPersonTraitInfoObject , nil
2024-04-11 15:51:56 +02:00
}