2024-04-11 15:51:56 +02:00
|
|
|
|
2024-06-15 02:43:01 +02:00
|
|
|
// createCoupleGeneticAnalysis provides functions to create a Couple genetic analysis
|
|
|
|
// Couple analyses provide an analysis of the prospective offspring of a pair of people
|
|
|
|
// These analyses are performed on one or more genome files.
|
|
|
|
// Analyses contain 3 categories of results: Monogenic Diseases, Polygenic Diseases and Traits
|
|
|
|
// Use createPersonGeneticAnalysis.go to create Person analyses
|
2024-04-11 15:51:56 +02:00
|
|
|
|
2024-06-15 02:43:01 +02:00
|
|
|
package createCoupleGeneticAnalysis
|
2024-04-11 15:51:56 +02:00
|
|
|
|
2024-06-05 06:10:35 +02:00
|
|
|
// Disclaimer: I am a novice in the ways of genetics. This package could be flawed in numerous ways.
|
2024-04-11 15:51:56 +02:00
|
|
|
|
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.
|
|
|
|
|
|
|
|
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"
|
2024-06-15 02:43:01 +02:00
|
|
|
import "seekia/internal/genetics/createPersonGeneticAnalysis"
|
2024-06-02 10:43:39 +02:00
|
|
|
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"
|
2024-06-05 06:10:35 +02:00
|
|
|
import mathRand "math/rand/v2"
|
2024-04-11 15:51:56 +02:00
|
|
|
import "slices"
|
2024-06-02 10:43:39 +02:00
|
|
|
import "maps"
|
2024-04-11 15:51:56 +02:00
|
|
|
|
|
|
|
|
|
|
|
//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-15 02:43:01 +02:00
|
|
|
person1DiseaseAnalysisObject, err := createPersonGeneticAnalysis.GetPersonMonogenicDiseaseAnalysis(person1GenomesWithMetadataList, diseaseObject)
|
2024-04-11 15:51:56 +02:00
|
|
|
if (err != nil) { return false, "", err }
|
|
|
|
|
2024-06-15 02:43:01 +02:00
|
|
|
person2DiseaseAnalysisObject, err := createPersonGeneticAnalysis.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-05 06:10:35 +02:00
|
|
|
//Outputs:
|
|
|
|
// -bool: Probability is known
|
|
|
|
// -float64: Probability that person will pass variant to offspring (between 0 and 1)
|
2024-04-11 15:51:56 +02:00
|
|
|
// -error
|
2024-06-05 06:10:35 +02:00
|
|
|
getPersonWillPassVariantProbability := func(personDiseaseAnalysisObject geneticAnalysis.PersonMonogenicDiseaseInfo, personGenomeIdentifier [16]byte)(bool, float64, error){
|
2024-04-11 15:51:56 +02:00
|
|
|
|
2024-06-05 06:10:35 +02:00
|
|
|
personDiseaseInfoMap := personDiseaseAnalysisObject.MonogenicDiseaseInfoMap
|
2024-06-02 10:43:39 +02:00
|
|
|
|
2024-06-05 06:10:35 +02:00
|
|
|
personGenomeDiseaseInfoObject, exists := personDiseaseInfoMap[personGenomeIdentifier]
|
|
|
|
if (exists == false){
|
|
|
|
return false, 0, nil
|
|
|
|
}
|
2024-04-11 15:51:56 +02:00
|
|
|
|
2024-06-05 06:10:35 +02:00
|
|
|
personGenomeDiseaseVariantsMap := personGenomeDiseaseInfoObject.VariantsInfoMap
|
2024-06-02 10:43:39 +02:00
|
|
|
|
2024-06-05 06:10:35 +02:00
|
|
|
personVariantInfoObject, exists := personGenomeDiseaseVariantsMap[variantIdentifier]
|
|
|
|
if (exists == false){
|
|
|
|
// The genome does not have information about this variant
|
2024-06-02 10:43:39 +02:00
|
|
|
|
2024-06-05 06:10:35 +02:00
|
|
|
return false, 0, nil
|
|
|
|
}
|
2024-06-02 10:43:39 +02:00
|
|
|
|
2024-06-05 06:10:35 +02:00
|
|
|
base1HasVariant := personVariantInfoObject.Base1HasVariant
|
|
|
|
base2HasVariant := personVariantInfoObject.Base2HasVariant
|
2024-06-02 10:43:39 +02:00
|
|
|
|
2024-06-05 06:10:35 +02:00
|
|
|
if (base1HasVariant == true && base2HasVariant == true){
|
|
|
|
return true, 1, nil
|
|
|
|
}
|
|
|
|
if (base1HasVariant == true && base2HasVariant == false){
|
|
|
|
return true, 0.5, nil
|
|
|
|
}
|
|
|
|
if (base1HasVariant == false && base2HasVariant == true){
|
|
|
|
return true, 0.5, nil
|
2024-04-11 15:51:56 +02:00
|
|
|
}
|
|
|
|
|
2024-06-05 06:10:35 +02:00
|
|
|
// Neither base has a variant
|
2024-04-11 15:51:56 +02:00
|
|
|
|
2024-06-05 06:10:35 +02:00
|
|
|
return true, 0, nil
|
|
|
|
}
|
2024-06-02 10:43:39 +02:00
|
|
|
|
2024-06-05 06:10:35 +02:00
|
|
|
person1VariantProbabilityIsKnown, person1WillPassVariantProbability, err := getPersonWillPassVariantProbability(person1DiseaseAnalysisObject, person1GenomeIdentifier)
|
|
|
|
if (err != nil) { return err }
|
2024-04-11 15:51:56 +02:00
|
|
|
|
2024-06-05 06:10:35 +02:00
|
|
|
person2VariantProbabilityIsKnown, person2WillPassVariantProbability, err := getPersonWillPassVariantProbability(person2DiseaseAnalysisObject, person2GenomeIdentifier)
|
|
|
|
if (err != nil) { return err }
|
2024-04-11 15:51:56 +02:00
|
|
|
|
2024-06-05 06:10:35 +02:00
|
|
|
if (person1VariantProbabilityIsKnown == false && person2VariantProbabilityIsKnown == false){
|
|
|
|
continue
|
|
|
|
}
|
2024-04-11 15:51:56 +02:00
|
|
|
|
2024-06-05 06:10:35 +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-05 06:10:35 +02:00
|
|
|
if (person1VariantProbabilityIsKnown == true && person2VariantProbabilityIsKnown == false){
|
2024-04-11 15:51:56 +02:00
|
|
|
|
2024-06-05 06:10:35 +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-05 06:10:35 +02:00
|
|
|
// Both people's probabilities are known
|
|
|
|
return person1WillPassVariantProbability, person1WillPassVariantProbability, person2WillPassVariantProbability, person2WillPassVariantProbability
|
|
|
|
}
|
2024-04-11 15:51:56 +02:00
|
|
|
|
2024-06-05 06:10:35 +02:00
|
|
|
bestCasePerson1WillPassVariantProbability, worstCasePerson1WillPassVariantProbability, bestCasePerson2WillPassVariantProbability, worstCasePerson2WillPassVariantProbability := getBestAndWorstCaseProbabilities()
|
2024-04-11 15:51:56 +02:00
|
|
|
|
2024-06-05 06:10:35 +02:00
|
|
|
//Outputs:
|
|
|
|
// -int: Percentage Probability of 0 mutations
|
|
|
|
// -int: Percentage Probability of 1 mutation
|
|
|
|
// -int: Percentage Probability of 2 mutations
|
|
|
|
// -error
|
|
|
|
getOffspringVariantMutationProbabilities := func(person1WillPassVariantProbability float64, person2WillPassVariantProbability float64)(int, int, int, error){
|
|
|
|
|
|
|
|
// This is the probability that neither person will pass the variant
|
|
|
|
// P = P(!A) * P(!B)
|
|
|
|
probabilityOf0Mutations := (1 - person1WillPassVariantProbability) * (1 - person2WillPassVariantProbability)
|
|
|
|
|
|
|
|
// This is the probability that either person will pass the variant, but not both
|
|
|
|
// P(A XOR B) = P(A) + P(B) - (2 * P(A and B))
|
|
|
|
probabilityOf1Mutation := person1WillPassVariantProbability + person2WillPassVariantProbability - (2 * person1WillPassVariantProbability * person2WillPassVariantProbability)
|
2024-04-11 15:51:56 +02:00
|
|
|
|
2024-06-05 06:10:35 +02:00
|
|
|
// This is the probability that both people will pass the variant
|
|
|
|
// P(A and B) = P(A) * P(B)
|
|
|
|
probabilityOf2Mutations := person1WillPassVariantProbability * person2WillPassVariantProbability
|
2024-04-11 15:51:56 +02:00
|
|
|
|
2024-06-05 06:10:35 +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 }
|
2024-04-11 15:51:56 +02:00
|
|
|
|
2024-06-05 06:10:35 +02:00
|
|
|
return percentageProbabilityOf0Mutations, percentageProbabilityOf1Mutation, percentageProbabilityOf2Mutations, nil
|
2024-04-11 15:51:56 +02:00
|
|
|
}
|
|
|
|
|
2024-06-05 06:10:35 +02:00
|
|
|
bestCase0MutationsProbability, bestCase1MutationProbability, bestCase2MutationsProbability, err := getOffspringVariantMutationProbabilities(bestCasePerson1WillPassVariantProbability, bestCasePerson2WillPassVariantProbability)
|
2024-04-11 15:51:56 +02:00
|
|
|
if (err != nil) { return err }
|
2024-06-05 06:10:35 +02:00
|
|
|
|
|
|
|
worstCase0MutationsProbability, worstCase1MutationProbability, worstCase2MutationsProbability, err := getOffspringVariantMutationProbabilities(worstCasePerson1WillPassVariantProbability, worstCasePerson2WillPassVariantProbability)
|
|
|
|
if (err != nil) { return err }
|
|
|
|
|
|
|
|
// 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
|
|
|
|
|
|
|
|
lowerBound1MutationProbability := min(bestCase1MutationProbability, worstCase1MutationProbability)
|
|
|
|
upperBound1MutationProbability := max(bestCase1MutationProbability, worstCase1MutationProbability)
|
2024-04-11 15:51:56 +02:00
|
|
|
|
2024-06-02 10:43:39 +02:00
|
|
|
newOffspringMonogenicDiseaseVariantInfoObject := geneticAnalysis.OffspringMonogenicDiseaseVariantInfo{
|
|
|
|
|
2024-06-05 06:10:35 +02:00
|
|
|
ProbabilityOf0MutationsLowerBound: worstCase0MutationsProbability,
|
|
|
|
ProbabilityOf0MutationsUpperBound: bestCase0MutationsProbability,
|
2024-06-02 10:43:39 +02:00
|
|
|
|
2024-06-05 06:10:35 +02:00
|
|
|
ProbabilityOf1MutationLowerBound: lowerBound1MutationProbability,
|
|
|
|
ProbabilityOf1MutationUpperBound: upperBound1MutationProbability,
|
2024-06-02 10:43:39 +02:00
|
|
|
|
2024-06-05 06:10:35 +02:00
|
|
|
ProbabilityOf2MutationsLowerBound: bestCase2MutationsProbability,
|
|
|
|
ProbabilityOf2MutationsUpperBound: worstCase2MutationsProbability,
|
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-15 02:43:01 +02:00
|
|
|
person1DiseaseAnalysisObject, err := createPersonGeneticAnalysis.GetPersonPolygenicDiseaseAnalysis(person1GenomesWithMetadataList, diseaseObject)
|
2024-04-11 15:51:56 +02:00
|
|
|
if (err != nil) { return false, "", err }
|
|
|
|
|
2024-06-15 02:43:01 +02:00
|
|
|
person2DiseaseAnalysisObject, err := createPersonGeneticAnalysis.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-07 02:04:13 +02:00
|
|
|
//Outputs:
|
|
|
|
// -bool: Any locus values exist
|
|
|
|
// -map[int64]locusValue.LocusValue
|
|
|
|
// -error
|
|
|
|
getPersonGenomeDiseaseLocusValuesMap := func(personGenomeIdentifier [16]byte, personDiseaseAnalysisObject geneticAnalysis.PersonPolygenicDiseaseInfo)(bool, map[int64]locusValue.LocusValue, error){
|
2024-04-11 15:51:56 +02:00
|
|
|
|
2024-06-07 02:04:13 +02:00
|
|
|
personPolygenicDiseaseInfoMap := personDiseaseAnalysisObject.PolygenicDiseaseInfoMap
|
2024-04-11 15:51:56 +02:00
|
|
|
|
2024-06-07 02:04:13 +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, nil
|
2024-06-02 10:43:39 +02:00
|
|
|
}
|
2024-04-11 15:51:56 +02:00
|
|
|
|
2024-06-07 02:04:13 +02:00
|
|
|
personGenomeLocusValuesMap := personGenomeDiseaseInfoObject.LocusValuesMap
|
2024-04-11 15:51:56 +02:00
|
|
|
|
2024-06-07 02:04:13 +02:00
|
|
|
return true, personGenomeLocusValuesMap, nil
|
|
|
|
}
|
2024-04-11 15:51:56 +02:00
|
|
|
|
2024-06-07 02:04:13 +02:00
|
|
|
anyPerson1LociValuesExist, person1LocusValuesMap, err := getPersonGenomeDiseaseLocusValuesMap(person1GenomeIdentifier, person1DiseaseAnalysisObject)
|
|
|
|
if (err != nil) { return err }
|
|
|
|
if (anyPerson1LociValuesExist == false){
|
|
|
|
// Offspring's disease info for this locus on this genome pair is unknown
|
|
|
|
return nil
|
2024-06-02 10:43:39 +02:00
|
|
|
}
|
2024-04-11 15:51:56 +02:00
|
|
|
|
2024-06-07 02:04:13 +02:00
|
|
|
anyPerson2LociValuesExist, person2LocusValuesMap, err := getPersonGenomeDiseaseLocusValuesMap(person2GenomeIdentifier, person2DiseaseAnalysisObject)
|
|
|
|
if (err != nil) { return err }
|
|
|
|
if (anyPerson2LociValuesExist == false){
|
|
|
|
// Offspring's disease info for this locus on this genome pair is unknown
|
|
|
|
return nil
|
|
|
|
}
|
2024-04-11 15:51:56 +02:00
|
|
|
|
2024-06-07 02:04:13 +02:00
|
|
|
anyOffspringLocusTested, genomePairOffspringAverageRiskScore, numberOfLociTested, genomePairOffspringDiseaseLociInfoMap, genomePairSampleOffspringRiskScoresList, err := GetOffspringPolygenicDiseaseInfo(diseaseLociList, person1LocusValuesMap, person2LocusValuesMap)
|
|
|
|
if (err != nil) { return err }
|
|
|
|
if (anyOffspringLocusTested == false){
|
2024-06-02 10:43:39 +02:00
|
|
|
// 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
|
|
|
newOffspringGenomePairPolygenicDiseaseInfo := geneticAnalysis.OffspringGenomePairPolygenicDiseaseInfo{
|
2024-04-11 15:51:56 +02:00
|
|
|
|
2024-06-02 10:43:39 +02:00
|
|
|
NumberOfLociTested: numberOfLociTested,
|
2024-06-07 02:04:13 +02:00
|
|
|
OffspringAverageRiskScore: genomePairOffspringAverageRiskScore,
|
|
|
|
LociInfoMap: genomePairOffspringDiseaseLociInfoMap,
|
|
|
|
SampleOffspringRiskScoresList: genomePairSampleOffspringRiskScoresList,
|
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
|
2024-06-07 02:04:13 +02:00
|
|
|
offspringAverageRiskScore := 0
|
2024-06-02 10:43:39 +02:00
|
|
|
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
|
2024-06-07 02:04:13 +02:00
|
|
|
genomePairOffspringAverageRiskScore := genomePairDiseaseInfoObject.OffspringAverageRiskScore
|
2024-06-02 10:43:39 +02:00
|
|
|
genomePairLociInfoMap := genomePairDiseaseInfoObject.LociInfoMap
|
2024-04-11 15:51:56 +02:00
|
|
|
|
2024-06-02 10:43:39 +02:00
|
|
|
if (firstItemReached == false){
|
|
|
|
numberOfLociTested = genomePairNumberOfLociTested
|
2024-06-07 02:04:13 +02:00
|
|
|
offspringAverageRiskScore = genomePairOffspringAverageRiskScore
|
2024-06-02 10:43:39 +02:00
|
|
|
offspringLociInfoMap = genomePairLociInfoMap
|
|
|
|
firstItemReached = true
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
if (numberOfLociTested != genomePairNumberOfLociTested){
|
|
|
|
return true, nil
|
|
|
|
}
|
2024-06-07 02:04:13 +02:00
|
|
|
if (offspringAverageRiskScore != genomePairOffspringAverageRiskScore){
|
2024-06-02 10:43:39 +02:00
|
|
|
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
|
|
|
|
|
2024-06-15 02:43:01 +02:00
|
|
|
person1TraitAnalysisObject, err := createPersonGeneticAnalysis.GetPersonTraitAnalysis(person1GenomesWithMetadataList, traitObject)
|
2024-04-11 15:51:56 +02:00
|
|
|
if (err != nil) { return false, "", err }
|
|
|
|
|
2024-06-15 02:43:01 +02:00
|
|
|
person2TraitAnalysisObject, err := createPersonGeneticAnalysis.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-05 06:10:35 +02:00
|
|
|
person1TraitInfoMap := person1TraitAnalysisObject.TraitInfoMap
|
|
|
|
person2TraitInfoMap := person2TraitAnalysisObject.TraitInfoMap
|
2024-04-11 15:51:56 +02:00
|
|
|
|
2024-06-05 06:10:35 +02:00
|
|
|
person1GenomeTraitInfoObject, exists := person1TraitInfoMap[person1GenomeIdentifier]
|
|
|
|
if (exists == false){
|
|
|
|
// This person has no genome values for any loci for this trait
|
|
|
|
// No predictions are possible
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
person2GenomeTraitInfoObject, exists := person2TraitInfoMap[person2GenomeIdentifier]
|
|
|
|
if (exists == false){
|
|
|
|
// This person has no genome values for any loci for this trait
|
|
|
|
// No predictions are possible
|
|
|
|
return nil
|
2024-04-11 15:51:56 +02:00
|
|
|
}
|
|
|
|
|
2024-06-05 06:10:35 +02:00
|
|
|
person1LocusValuesMap := person1GenomeTraitInfoObject.LocusValuesMap
|
|
|
|
person2LocusValuesMap := person2GenomeTraitInfoObject.LocusValuesMap
|
2024-04-11 15:51:56 +02:00
|
|
|
|
2024-06-05 06:10:35 +02:00
|
|
|
anyRulesTested, numberOfRulesTested, offspringProbabilityOfPassingRulesMap, offspringAverageOutcomeScoresMap, err := GetOffspringTraitInfo(traitObject, person1LocusValuesMap, person2LocusValuesMap)
|
|
|
|
if (err != nil) { return err }
|
|
|
|
if (anyRulesTested == false){
|
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
|
|
|
|
}
|
|
|
|
|
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
|
|
|
|
}
|
|
|
|
|
2024-06-07 02:04:13 +02:00
|
|
|
|
|
|
|
// We also use this function when calculating offspring probabilities between users in viewProfileGui.go
|
2024-06-05 06:10:35 +02:00
|
|
|
//Outputs:
|
2024-06-07 02:04:13 +02:00
|
|
|
// -bool: Probability offspring has disease is known
|
|
|
|
// -int: Percentage probability offspring has disease (0-100)
|
|
|
|
// -bool: Probability offspring has variant is known
|
|
|
|
// -int: Percentage probability offspring has variant (0-100)
|
2024-06-05 06:10:35 +02:00
|
|
|
// -error
|
2024-06-07 02:04:13 +02:00
|
|
|
func GetOffspringMonogenicDiseaseProbabilities(dominantOrRecessive string, person1ProbabilityIsKnown bool, person1WillPassVariantPercentageProbability int, person2ProbabilityIsKnown bool, person2WillPassVariantPercentageProbability int)(bool, int, bool, int, error){
|
2024-06-05 06:10:35 +02:00
|
|
|
|
2024-06-07 02:04:13 +02:00
|
|
|
if (dominantOrRecessive != "Dominant" && dominantOrRecessive != "Recessive"){
|
|
|
|
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-06-05 06:10:35 +02:00
|
|
|
|
2024-06-07 02:04:13 +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")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (person1ProbabilityIsKnown == false || person2ProbabilityIsKnown == false){
|
|
|
|
// Only 1 of the person's probabilities are known
|
2024-06-05 06:10:35 +02:00
|
|
|
|
2024-06-07 02:04:13 +02:00
|
|
|
getPersonWhoHasVariantProbabilityOfPassingIt := func()int{
|
|
|
|
if (person1ProbabilityIsKnown == true){
|
|
|
|
return person1WillPassVariantPercentageProbability
|
|
|
|
}
|
|
|
|
return person2WillPassVariantPercentageProbability
|
|
|
|
}
|
2024-06-05 06:10:35 +02:00
|
|
|
|
2024-06-07 02:04:13 +02:00
|
|
|
personWhoHasVariantProbabilityOfPassingIt := getPersonWhoHasVariantProbabilityOfPassingIt()
|
2024-06-05 06:10:35 +02:00
|
|
|
|
2024-06-07 02:04:13 +02:00
|
|
|
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
|
2024-06-05 06:10:35 +02:00
|
|
|
}
|
2024-06-07 02:04:13 +02:00
|
|
|
//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){
|
2024-06-05 06:10:35 +02:00
|
|
|
|
2024-06-07 02:04:13 +02:00
|
|
|
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
|
|
|
|
}
|
2024-06-05 06:10:35 +02:00
|
|
|
}
|
|
|
|
|
2024-06-07 02:04:13 +02:00
|
|
|
// 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-06-05 06:10:35 +02:00
|
|
|
|
2024-06-07 02:04:13 +02:00
|
|
|
person1WillPassVariantProbability := float64(person1WillPassVariantPercentageProbability)/100
|
|
|
|
person2WillPassVariantProbability := float64(person2WillPassVariantPercentageProbability)/100
|
2024-06-05 06:10:35 +02:00
|
|
|
|
2024-06-07 02:04:13 +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)
|
|
|
|
// (Probability of person 1 passing a variant) + (Probability of person 2 passing a variant) - (Probability of offspring having disease)
|
|
|
|
// A person with a variant may have the disease, or just be a carrier.
|
|
|
|
probabilityOffspringHasVariant := person1WillPassVariantProbability + person2WillPassVariantProbability - (person1WillPassVariantProbability * person2WillPassVariantProbability)
|
2024-06-05 06:10:35 +02:00
|
|
|
|
2024-06-07 02:04:13 +02:00
|
|
|
if (dominantOrRecessive == "Dominant"){
|
2024-06-05 06:10:35 +02:00
|
|
|
|
2024-06-07 02:04:13 +02:00
|
|
|
// The probability of having the monogenic disease is the same as the probability of having a variant
|
2024-06-05 06:10:35 +02:00
|
|
|
|
2024-06-07 02:04:13 +02:00
|
|
|
percentageProbabilityOffspringHasVariant := int(probabilityOffspringHasVariant * 100)
|
2024-06-05 06:10:35 +02:00
|
|
|
|
2024-06-07 02:04:13 +02:00
|
|
|
return true, percentageProbabilityOffspringHasVariant, true, percentageProbabilityOffspringHasVariant, nil
|
2024-06-05 06:10:35 +02:00
|
|
|
}
|
|
|
|
|
2024-06-07 02:04:13 +02:00
|
|
|
// We find the probability of the offspring having the mongenic disease as follows:
|
|
|
|
// P(A and B) = P(A) * P(B)
|
|
|
|
// (Probability of person 1 Passing a variant) * (Probability of person 2 passing a variant)
|
|
|
|
probabilityOffspringHasDisease := person1WillPassVariantProbability * person2WillPassVariantProbability
|
2024-06-05 06:10:35 +02:00
|
|
|
|
2024-06-07 02:04:13 +02:00
|
|
|
percentageProbabilityOffspringHasDisease := probabilityOffspringHasDisease * 100
|
|
|
|
percentageProbabilityOffspringHasVariant := probabilityOffspringHasVariant * 100
|
2024-06-05 06:10:35 +02:00
|
|
|
|
2024-06-07 02:04:13 +02:00
|
|
|
// 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-05 06:10:35 +02:00
|
|
|
|
2024-06-07 02:04:13 +02:00
|
|
|
return true, percentageProbabilityOffspringHasDiseaseInt, true, percentageProbabilityOffspringHasVariantInt, nil
|
|
|
|
}
|
2024-06-05 06:10:35 +02:00
|
|
|
|
|
|
|
|
2024-06-07 02:04:13 +02:00
|
|
|
// This is used to calculate user polygenic disease info for users
|
|
|
|
// It is faster to do it this way, because we don't create 100 prospective offspring
|
|
|
|
// We instead create 4 outcomes for each locus
|
|
|
|
// We can do this because testing each locus's risk score is independent of every other locus
|
|
|
|
// This is not true for traits, because trait rules are effected by multiple different loci
|
|
|
|
// When using the fast method for polygenic diseases, we don't get a sample of 100 offspring disease risk scores.
|
|
|
|
// The average risk score should still be the same for the fast and normal methods
|
|
|
|
// This function is also faster because we don't calculate odds ratio information or information about each locus
|
|
|
|
//Outputs:
|
|
|
|
// -bool: Any loci tested (if false, no offspring polygenic disease information is known)
|
|
|
|
// -int: Offspring Risk Score (Value between 0-10)
|
|
|
|
// -int: Number of loci tested
|
|
|
|
// -error
|
|
|
|
func GetOffspringPolygenicDiseaseInfo_Fast(diseaseLociList []polygenicDiseases.DiseaseLocus, person1LocusValuesMap map[int64]locusValue.LocusValue, person2LocusValuesMap map[int64]locusValue.LocusValue)(bool, int, int, error){
|
2024-06-05 06:10:35 +02:00
|
|
|
|
2024-06-07 02:04:13 +02:00
|
|
|
if (len(person1LocusValuesMap) == 0){
|
|
|
|
return false, 0, 0, nil
|
|
|
|
}
|
|
|
|
if (len(person2LocusValuesMap) == 0){
|
|
|
|
return false, 0, 0, nil
|
|
|
|
}
|
2024-06-05 06:10:35 +02:00
|
|
|
|
2024-06-15 02:43:01 +02:00
|
|
|
// I = Insertion
|
|
|
|
// D = Deletion
|
|
|
|
|
|
|
|
validAllelesList := []string{"C", "A", "T", "G", "I", "D"}
|
|
|
|
|
2024-06-07 02:04:13 +02:00
|
|
|
numberOfLociTested := 0
|
2024-06-05 06:10:35 +02:00
|
|
|
|
2024-06-07 02:04:13 +02:00
|
|
|
offspringSummedRiskWeights := 0
|
|
|
|
offspringMinimumPossibleRiskWeightSum := 0
|
|
|
|
offspringMaximumPossibleRiskWeightSum := 0
|
2024-06-05 06:10:35 +02:00
|
|
|
|
2024-06-07 02:04:13 +02:00
|
|
|
for _, locusObject := range diseaseLociList{
|
2024-06-05 06:10:35 +02:00
|
|
|
|
2024-06-07 02:04:13 +02:00
|
|
|
locusRSID := locusObject.LocusRSID
|
|
|
|
locusRiskWeightsMap := locusObject.RiskWeightsMap
|
|
|
|
locusMinimumWeight := locusObject.MinimumRiskWeight
|
|
|
|
locusMaximumWeight := locusObject.MaximumRiskWeight
|
2024-06-05 06:10:35 +02:00
|
|
|
|
2024-06-15 02:43:01 +02:00
|
|
|
person1LocusValueFound, person1LocusBase1Value, person1LocusBase2Value, _, _, err := createPersonGeneticAnalysis.GetLocusValueFromGenomeMap(true, person1LocusValuesMap, locusRSID)
|
2024-06-07 02:04:13 +02:00
|
|
|
if (err != nil) { return false, 0, 0, err }
|
|
|
|
if (person1LocusValueFound == false){
|
|
|
|
// None of the offspring will have a value for this locus
|
|
|
|
continue
|
|
|
|
}
|
2024-06-05 06:10:35 +02:00
|
|
|
|
2024-06-15 02:43:01 +02:00
|
|
|
person2LocusValueFound, person2LocusBase1Value, person2LocusBase2Value, _, _, err := createPersonGeneticAnalysis.GetLocusValueFromGenomeMap(true, person2LocusValuesMap, locusRSID)
|
2024-06-07 02:04:13 +02:00
|
|
|
if (err != nil) { return false, 0, 0, err }
|
|
|
|
if (person2LocusValueFound == false){
|
|
|
|
// None of the offspring will have a value for this locus
|
|
|
|
continue
|
|
|
|
}
|
2024-06-05 06:10:35 +02:00
|
|
|
|
2024-06-15 02:43:01 +02:00
|
|
|
baseIsValid := slices.Contains(validAllelesList, person1LocusBase1Value)
|
|
|
|
if (baseIsValid == false){
|
|
|
|
return false, 0, 0, errors.New("GetOffspringPolygenicDiseaseInfo_Fast called with genomeMap containing invalid locus value base: " + person1LocusBase1Value)
|
|
|
|
}
|
|
|
|
baseIsValid = slices.Contains(validAllelesList, person1LocusBase2Value)
|
|
|
|
if (baseIsValid == false){
|
|
|
|
return false, 0, 0, errors.New("GetOffspringPolygenicDiseaseInfo_Fast called with genomeMap containing invalid locus value base: " + person1LocusBase2Value)
|
|
|
|
}
|
|
|
|
baseIsValid = slices.Contains(validAllelesList, person2LocusBase1Value)
|
|
|
|
if (baseIsValid == false){
|
|
|
|
return false, 0, 0, errors.New("GetOffspringPolygenicDiseaseInfo_Fast called with genomeMap containing invalid locus value base: " + person2LocusBase1Value)
|
|
|
|
}
|
|
|
|
baseIsValid = slices.Contains(validAllelesList, person2LocusBase2Value)
|
|
|
|
if (baseIsValid == false){
|
|
|
|
return false, 0, 0, errors.New("GetOffspringPolygenicDiseaseInfo_Fast called with genomeMap containing invalid locus value base: " + person2LocusBase2Value)
|
|
|
|
}
|
|
|
|
|
2024-06-07 02:04:13 +02:00
|
|
|
numberOfLociTested += 1
|
2024-06-05 06:10:35 +02:00
|
|
|
|
2024-06-07 02:04:13 +02:00
|
|
|
offspringBasePairOutcome1 := person1LocusBase1Value + ";" + person2LocusBase1Value
|
|
|
|
offspringBasePairOutcome2 := person1LocusBase2Value + ";" + person2LocusBase2Value
|
|
|
|
offspringBasePairOutcome3 := person1LocusBase1Value + ";" + person2LocusBase2Value
|
|
|
|
offspringBasePairOutcome4 := person1LocusBase2Value + ";" + person2LocusBase1Value
|
2024-06-05 06:10:35 +02:00
|
|
|
|
2024-06-07 02:04:13 +02:00
|
|
|
baseOutcomesList := []string{offspringBasePairOutcome1, offspringBasePairOutcome2, offspringBasePairOutcome3, offspringBasePairOutcome4}
|
2024-06-05 06:10:35 +02:00
|
|
|
|
2024-06-07 02:04:13 +02:00
|
|
|
outcomesSummedRiskWeight := 0
|
2024-06-05 06:10:35 +02:00
|
|
|
|
2024-06-07 02:04:13 +02:00
|
|
|
for _, outcomeBasePair := range baseOutcomesList{
|
2024-06-05 06:10:35 +02:00
|
|
|
|
2024-06-07 02:04:13 +02:00
|
|
|
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 weight
|
2024-06-05 06:10:35 +02:00
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
2024-06-07 02:04:13 +02:00
|
|
|
outcomesSummedRiskWeight += offspringOutcomeRiskWeight
|
|
|
|
}
|
2024-06-05 06:10:35 +02:00
|
|
|
|
2024-06-07 02:04:13 +02:00
|
|
|
locusAverageRiskWeight := outcomesSummedRiskWeight/4
|
2024-06-05 06:10:35 +02:00
|
|
|
|
2024-06-07 02:04:13 +02:00
|
|
|
offspringSummedRiskWeights += locusAverageRiskWeight
|
2024-06-05 06:10:35 +02:00
|
|
|
|
2024-06-07 02:04:13 +02:00
|
|
|
offspringMinimumPossibleRiskWeightSum += locusMinimumWeight
|
|
|
|
offspringMaximumPossibleRiskWeightSum += locusMaximumWeight
|
|
|
|
}
|
2024-06-05 06:10:35 +02:00
|
|
|
|
2024-06-07 02:04:13 +02:00
|
|
|
offspringAverageDiseaseRiskScore, err := helpers.ScaleNumberProportionally(true, offspringSummedRiskWeights, offspringMinimumPossibleRiskWeightSum, offspringMaximumPossibleRiskWeightSum, 0, 10)
|
|
|
|
if (err != nil) { return false, 0, 0, err }
|
|
|
|
|
|
|
|
if (numberOfLociTested == 0){
|
|
|
|
// No locations were tested
|
|
|
|
return false, 0, 0, nil
|
2024-06-05 06:10:35 +02:00
|
|
|
}
|
|
|
|
|
2024-06-07 02:04:13 +02:00
|
|
|
return true, offspringAverageDiseaseRiskScore, numberOfLociTested, nil
|
2024-06-05 06:10:35 +02:00
|
|
|
}
|
|
|
|
|
2024-06-15 02:43:01 +02:00
|
|
|
|
2024-04-11 15:51:56 +02:00
|
|
|
//Outputs:
|
2024-06-07 02:04:13 +02:00
|
|
|
// -bool: Any loci tested (if false, no offspring polygenic disease information is known)
|
|
|
|
// -int: Offspring Risk Score (Value between 0-10)
|
|
|
|
// -int: Number of loci tested
|
|
|
|
// -map[[3]byte]geneticAnalysis.OffspringPolygenicDiseaseLocusInfo: Offspring Locus information map
|
|
|
|
// Map Structure: Locus identifier -> OffspringPolygenicDiseaseLocusInfo
|
|
|
|
// -[]int: Sample offspring risks scores list
|
2024-04-11 15:51:56 +02:00
|
|
|
// -error
|
2024-06-07 02:04:13 +02:00
|
|
|
func GetOffspringPolygenicDiseaseInfo(diseaseLociList []polygenicDiseases.DiseaseLocus, person1LocusValuesMap map[int64]locusValue.LocusValue, person2LocusValuesMap map[int64]locusValue.LocusValue)(bool, int, int, map[[3]byte]geneticAnalysis.OffspringPolygenicDiseaseLocusInfo, []int, error){
|
2024-04-11 15:51:56 +02:00
|
|
|
|
2024-06-07 02:04:13 +02:00
|
|
|
if (len(person1LocusValuesMap) == 0){
|
|
|
|
return false, 0, 0, nil, nil, nil
|
2024-06-02 10:43:39 +02:00
|
|
|
}
|
2024-06-07 02:04:13 +02:00
|
|
|
if (len(person2LocusValuesMap) == 0){
|
|
|
|
return false, 0, 0, nil, nil, nil
|
2024-04-11 15:51:56 +02:00
|
|
|
}
|
|
|
|
|
2024-06-07 02:04:13 +02:00
|
|
|
// First, we create 100 prospective offspring genomes.
|
|
|
|
|
|
|
|
diseaseLociRSIDsList := make([]int64, 0)
|
|
|
|
|
|
|
|
for _, diseaseLocusObject := range diseaseLociList{
|
|
|
|
|
|
|
|
locusRSID := diseaseLocusObject.LocusRSID
|
|
|
|
diseaseLociRSIDsList = append(diseaseLociRSIDsList, locusRSID)
|
2024-06-02 10:43:39 +02:00
|
|
|
}
|
2024-06-07 02:04:13 +02:00
|
|
|
|
|
|
|
anyLocusValueExists, prospectiveOffspringGenomesList, err := getProspectiveOffspringGenomesList(diseaseLociRSIDsList, person1LocusValuesMap, person2LocusValuesMap)
|
|
|
|
if (err != nil) { return false, 0, 0, nil, nil, err }
|
|
|
|
if (anyLocusValueExists == false){
|
|
|
|
return false, 0, 0, nil, nil, nil
|
2024-04-11 15:51:56 +02:00
|
|
|
}
|
2024-06-02 10:43:39 +02:00
|
|
|
|
2024-06-07 02:04:13 +02:00
|
|
|
// This will sum every offspring's average disease risk score
|
|
|
|
offspringAverageRiskScoreSum := 0
|
2024-04-11 15:51:56 +02:00
|
|
|
|
2024-06-07 02:04:13 +02:00
|
|
|
// This stores a list of every prospective offspring's risk score
|
|
|
|
sampleOffspringRiskScoresList := make([]int, 0)
|
2024-06-02 10:43:39 +02:00
|
|
|
|
2024-06-07 02:04:13 +02:00
|
|
|
type offspringSummedLocusInfoObject struct{
|
2024-06-02 10:43:39 +02:00
|
|
|
|
2024-06-07 02:04:13 +02:00
|
|
|
SummedLocusRiskWeights int
|
2024-06-02 10:43:39 +02:00
|
|
|
|
2024-06-07 02:04:13 +02:00
|
|
|
SummedOddsRatios float64
|
2024-04-11 15:51:56 +02:00
|
|
|
|
2024-06-07 02:04:13 +02:00
|
|
|
NumberOfSummedOddsRatios int
|
2024-06-02 10:43:39 +02:00
|
|
|
|
2024-06-07 02:04:13 +02:00
|
|
|
// This is the number of unknown-odds-ratio-weight sums that we summed up
|
|
|
|
NumberOfUnknownOddsRatioWeightSums int
|
2024-04-11 15:51:56 +02:00
|
|
|
|
2024-06-07 02:04:13 +02:00
|
|
|
// This is the sum of every unknown-odds-ratio-weight-sum for each prospective offspring for this genome
|
|
|
|
UnknownOddsRatioWeightSumsSummed int
|
|
|
|
}
|
2024-04-11 15:51:56 +02:00
|
|
|
|
2024-06-07 02:04:13 +02:00
|
|
|
// Map Structure: Locus Identifier -> offspringSummedLocusInfoObject
|
|
|
|
offspringLocusInfoSumsMap := make(map[[3]byte]offspringSummedLocusInfoObject)
|
2024-04-11 15:51:56 +02:00
|
|
|
|
2024-06-07 02:04:13 +02:00
|
|
|
for offspringIndex, offspringGenomeMap := range prospectiveOffspringGenomesList{
|
2024-04-11 15:51:56 +02:00
|
|
|
|
2024-06-07 02:04:13 +02:00
|
|
|
offspringSummedRiskWeights := 0
|
|
|
|
offspringMinimumPossibleRiskWeightSum := 0
|
|
|
|
offspringMaximumPossibleRiskWeightSum := 0
|
2024-04-11 15:51:56 +02:00
|
|
|
|
2024-06-07 02:04:13 +02:00
|
|
|
for _, locusObject := range diseaseLociList{
|
2024-04-11 15:51:56 +02:00
|
|
|
|
2024-06-07 02:04:13 +02:00
|
|
|
locusIdentifierHex := locusObject.LocusIdentifier
|
2024-04-11 15:51:56 +02:00
|
|
|
|
2024-06-07 02:04:13 +02:00
|
|
|
locusIdentifier, err := encoding.DecodeHexStringTo3ByteArray(locusIdentifierHex)
|
|
|
|
if (err != nil) { return false, 0, 0, nil, nil, err }
|
2024-04-11 15:51:56 +02:00
|
|
|
|
2024-06-07 02:04:13 +02:00
|
|
|
offspringLocusInfoSumsObject, exists := offspringLocusInfoSumsMap[locusIdentifier]
|
|
|
|
if (exists == false){
|
2024-04-11 15:51:56 +02:00
|
|
|
|
2024-06-07 02:04:13 +02:00
|
|
|
if (offspringIndex != 0){
|
|
|
|
// We already checked a previous offspring for this locus, and it's value doesn't exist
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
}
|
2024-04-11 15:51:56 +02:00
|
|
|
|
2024-06-07 02:04:13 +02:00
|
|
|
locusRSID := locusObject.LocusRSID
|
|
|
|
locusRiskWeightsMap := locusObject.RiskWeightsMap
|
|
|
|
locusOddsRatiosMap := locusObject.OddsRatiosMap
|
|
|
|
locusMinimumWeight := locusObject.MinimumRiskWeight
|
|
|
|
locusMaximumWeight := locusObject.MaximumRiskWeight
|
2024-04-11 15:51:56 +02:00
|
|
|
|
2024-06-15 02:43:01 +02:00
|
|
|
basePairValueFound, locusBase1Value, locusBase2Value, _, _, err := createPersonGeneticAnalysis.GetLocusValueFromGenomeMap(true, offspringGenomeMap, locusRSID)
|
2024-06-07 02:04:13 +02:00
|
|
|
if (err != nil) { return false, 0, 0, nil, nil, err }
|
|
|
|
if (basePairValueFound == false){
|
|
|
|
// None of the offspring will have a value for this locus
|
|
|
|
continue
|
|
|
|
}
|
2024-04-11 15:51:56 +02:00
|
|
|
|
2024-06-15 02:43:01 +02:00
|
|
|
locusRiskWeight, locusOddsRatioIsKnown, locusOddsRatio, err := createPersonGeneticAnalysis.GetGenomePolygenicDiseaseLocusRiskInfo(locusRiskWeightsMap, locusOddsRatiosMap, locusBase1Value, locusBase2Value)
|
2024-06-07 02:04:13 +02:00
|
|
|
if (err != nil) { return false, 0, 0, nil, nil, err }
|
2024-04-11 15:51:56 +02:00
|
|
|
|
2024-06-07 02:04:13 +02:00
|
|
|
offspringLocusInfoSumsObject.SummedLocusRiskWeights += locusRiskWeight
|
2024-04-11 15:51:56 +02:00
|
|
|
|
2024-06-07 02:04:13 +02:00
|
|
|
if (locusOddsRatioIsKnown == true){
|
|
|
|
offspringLocusInfoSumsObject.SummedOddsRatios += locusOddsRatio
|
|
|
|
offspringLocusInfoSumsObject.NumberOfSummedOddsRatios += 1
|
|
|
|
} else {
|
|
|
|
offspringLocusInfoSumsObject.UnknownOddsRatioWeightSumsSummed += locusRiskWeight
|
|
|
|
offspringLocusInfoSumsObject.NumberOfUnknownOddsRatioWeightSums += 1
|
|
|
|
}
|
2024-04-11 15:51:56 +02:00
|
|
|
|
2024-06-07 02:04:13 +02:00
|
|
|
offspringLocusInfoSumsMap[locusIdentifier] = offspringLocusInfoSumsObject
|
2024-04-11 15:51:56 +02:00
|
|
|
|
2024-06-07 02:04:13 +02:00
|
|
|
offspringSummedRiskWeights += locusRiskWeight
|
2024-04-11 15:51:56 +02:00
|
|
|
|
2024-06-07 02:04:13 +02:00
|
|
|
offspringMinimumPossibleRiskWeightSum += locusMinimumWeight
|
|
|
|
offspringMaximumPossibleRiskWeightSum += locusMaximumWeight
|
2024-04-11 15:51:56 +02:00
|
|
|
}
|
|
|
|
|
2024-06-07 02:04:13 +02:00
|
|
|
offspringAverageDiseaseRiskScore, err := helpers.ScaleNumberProportionally(true, offspringSummedRiskWeights, offspringMinimumPossibleRiskWeightSum, offspringMaximumPossibleRiskWeightSum, 0, 10)
|
|
|
|
if (err != nil) { return false, 0, 0, nil, nil, err }
|
2024-04-11 15:51:56 +02:00
|
|
|
|
2024-06-07 02:04:13 +02:00
|
|
|
sampleOffspringRiskScoresList = append(sampleOffspringRiskScoresList, offspringAverageDiseaseRiskScore)
|
2024-04-11 15:51:56 +02:00
|
|
|
|
2024-06-07 02:04:13 +02:00
|
|
|
offspringAverageRiskScoreSum += offspringAverageDiseaseRiskScore
|
|
|
|
}
|
|
|
|
|
|
|
|
numberOfLociTested := len(offspringLocusInfoSumsMap)
|
|
|
|
|
|
|
|
if (numberOfLociTested == 0){
|
|
|
|
// No locations were tested
|
|
|
|
return false, 0, 0, nil, nil, nil
|
2024-04-11 15:51:56 +02:00
|
|
|
}
|
|
|
|
|
2024-06-07 02:04:13 +02:00
|
|
|
offspringAverageRiskScore := offspringAverageRiskScoreSum/100
|
|
|
|
|
|
|
|
// Map Structure: Locus Identifier -> OffspringPolygenicDiseaseLocusInfo
|
|
|
|
offspringDiseaseLociInfoMap := make(map[[3]byte]geneticAnalysis.OffspringPolygenicDiseaseLocusInfo)
|
|
|
|
|
|
|
|
for locusIdentifier, summedLocusInfoObject := range offspringLocusInfoSumsMap{
|
|
|
|
|
|
|
|
summedLocusRiskWeights := summedLocusInfoObject.SummedLocusRiskWeights
|
|
|
|
summedOddsRatios := summedLocusInfoObject.SummedOddsRatios
|
|
|
|
numberOfSummedOddsRatios := summedLocusInfoObject.NumberOfSummedOddsRatios
|
|
|
|
numberOfUnknownOddsRatioWeightSums := summedLocusInfoObject.NumberOfUnknownOddsRatioWeightSums
|
|
|
|
unknownOddsRatioWeightSumsSummed := summedLocusInfoObject.UnknownOddsRatioWeightSumsSummed
|
2024-04-11 15:51:56 +02:00
|
|
|
|
2024-06-07 02:04:13 +02:00
|
|
|
// There are 100 prospective offspring, so we divide by 100
|
|
|
|
locusAverageRiskWeight := summedLocusRiskWeights/100
|
2024-04-11 15:51:56 +02:00
|
|
|
|
2024-06-07 02:04:13 +02:00
|
|
|
newLocusInfoObject := geneticAnalysis.OffspringPolygenicDiseaseLocusInfo{
|
|
|
|
OffspringAverageRiskWeight: locusAverageRiskWeight,
|
2024-04-11 15:51:56 +02:00
|
|
|
}
|
|
|
|
|
2024-06-07 02:04:13 +02:00
|
|
|
if (numberOfSummedOddsRatios != 0){
|
|
|
|
newLocusInfoObject.OffspringOddsRatioIsKnown = true
|
2024-04-11 15:51:56 +02:00
|
|
|
|
2024-06-07 02:04:13 +02:00
|
|
|
offspringAverageOddsRatio := summedOddsRatios/float64(numberOfSummedOddsRatios)
|
2024-04-11 15:51:56 +02:00
|
|
|
|
2024-06-07 02:04:13 +02:00
|
|
|
newLocusInfoObject.OffspringAverageOddsRatio = offspringAverageOddsRatio
|
|
|
|
}
|
|
|
|
|
|
|
|
if (numberOfUnknownOddsRatioWeightSums != 0){
|
2024-04-11 15:51:56 +02:00
|
|
|
|
2024-06-07 02:04:13 +02:00
|
|
|
offspringAverageUnknownOddsRatiosWeightSum := unknownOddsRatioWeightSumsSummed/numberOfUnknownOddsRatioWeightSums
|
|
|
|
|
|
|
|
newLocusInfoObject.OffspringAverageUnknownOddsRatiosWeightSum = offspringAverageUnknownOddsRatiosWeightSum
|
|
|
|
}
|
|
|
|
|
|
|
|
offspringDiseaseLociInfoMap[locusIdentifier] = newLocusInfoObject
|
|
|
|
}
|
2024-04-11 15:51:56 +02:00
|
|
|
|
2024-06-07 02:04:13 +02:00
|
|
|
return true, offspringAverageRiskScore, numberOfLociTested, offspringDiseaseLociInfoMap, sampleOffspringRiskScoresList, nil
|
2024-04-11 15:51:56 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2024-06-05 06:10:35 +02:00
|
|
|
//Outputs:
|
|
|
|
// -bool: Any rules tested (if false, no offspring trait information is known)
|
|
|
|
// -int: Number of rules tested
|
|
|
|
// -map[[3]byte]int: Offspring probability of passing rules map
|
|
|
|
// Map Structure: Rule identifier -> Offspring probability of passing rule (1-100)
|
|
|
|
// -map[string]float64: Offspring average outcome scores map
|
|
|
|
// Map Structure: Outcome Name -> Offspring average outcome score
|
2024-04-11 15:51:56 +02:00
|
|
|
// -error
|
2024-06-05 06:10:35 +02:00
|
|
|
func GetOffspringTraitInfo(traitObject traits.Trait, person1LocusValuesMap map[int64]locusValue.LocusValue, person2LocusValuesMap map[int64]locusValue.LocusValue)(bool, int, map[[3]byte]int, map[string]float64, error){
|
2024-04-11 15:51:56 +02:00
|
|
|
|
2024-06-07 02:04:13 +02:00
|
|
|
if (len(person1LocusValuesMap) == 0){
|
|
|
|
return false, 0, nil, nil, nil
|
|
|
|
}
|
|
|
|
if (len(person2LocusValuesMap) == 0){
|
|
|
|
return false, 0, nil, nil, nil
|
|
|
|
}
|
|
|
|
|
2024-06-05 06:10:35 +02:00
|
|
|
// First, we create 100 prospective offspring genomes.
|
2024-04-11 15:51:56 +02:00
|
|
|
|
2024-06-05 06:10:35 +02:00
|
|
|
traitLociList := traitObject.LociList
|
2024-04-11 15:51:56 +02:00
|
|
|
|
2024-06-05 06:10:35 +02:00
|
|
|
anyLocusValueExists, prospectiveOffspringGenomesList, err := getProspectiveOffspringGenomesList(traitLociList, person1LocusValuesMap, person2LocusValuesMap)
|
|
|
|
if (err != nil) { return false, 0, nil, nil, err }
|
|
|
|
if (anyLocusValueExists == false){
|
|
|
|
return false, 0, nil, nil, nil
|
|
|
|
}
|
2024-04-11 15:51:56 +02:00
|
|
|
|
2024-06-05 06:10:35 +02:00
|
|
|
traitRulesList := traitObject.RulesList
|
2024-04-11 15:51:56 +02:00
|
|
|
|
2024-06-05 06:10:35 +02:00
|
|
|
// Map Structure: Rule Identifier -> Number of offspring who pass the rule (out of 100 prospective offspring)
|
|
|
|
offspringPassesRulesCountMap := make(map[[3]byte]int)
|
2024-04-11 15:51:56 +02:00
|
|
|
|
2024-06-05 06:10:35 +02:00
|
|
|
// We use this map to keep track of the rules for which we know every offspring's passes-rule status
|
|
|
|
// Map Structure: Rule Identifier -> Rule Object
|
|
|
|
offspringRulesWithKnownStatusMap := make(map[[3]byte]traits.TraitRule)
|
|
|
|
|
|
|
|
for offspringIndex, offspringGenomeMap := range prospectiveOffspringGenomesList{
|
|
|
|
|
|
|
|
// We iterate through rules to determine genome pair trait info
|
|
|
|
|
|
|
|
for _, ruleObject := range traitRulesList{
|
|
|
|
|
|
|
|
ruleIdentifierHex := ruleObject.RuleIdentifier
|
|
|
|
|
|
|
|
ruleIdentifier, err := encoding.DecodeHexStringTo3ByteArray(ruleIdentifierHex)
|
|
|
|
if (err != nil) { return false, 0, nil, nil, err }
|
|
|
|
|
2024-06-07 02:04:13 +02:00
|
|
|
if (offspringIndex == 0){
|
|
|
|
|
|
|
|
offspringRulesWithKnownStatusMap[ruleIdentifier] = ruleObject
|
|
|
|
} else {
|
2024-06-05 06:10:35 +02:00
|
|
|
|
|
|
|
_, exists := offspringRulesWithKnownStatusMap[ruleIdentifier]
|
|
|
|
if (exists == false){
|
|
|
|
// We already tried to check a previous offspring's passes-rule status for this rule
|
|
|
|
// We know that the offspring's passes-rule status will be unknown for every prospective offspring
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// This is a list that describes the locus rsids and their values that must be fulfilled to pass the rule
|
|
|
|
ruleLocusObjectsList := ruleObject.LociList
|
|
|
|
|
2024-06-15 02:43:01 +02:00
|
|
|
offspringPassesRuleIsKnown, offspringPassesRule, err := createPersonGeneticAnalysis.GetGenomePassesTraitRuleStatus(ruleLocusObjectsList, offspringGenomeMap, false)
|
2024-06-07 02:04:13 +02:00
|
|
|
if (err != nil){ return false, 0, nil, nil, err }
|
2024-06-05 06:10:35 +02:00
|
|
|
if (offspringPassesRuleIsKnown == false){
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
if (offspringPassesRule == true){
|
|
|
|
offspringPassesRulesCountMap[ruleIdentifier] += 1
|
|
|
|
}
|
2024-04-11 15:51:56 +02:00
|
|
|
}
|
2024-06-05 06:10:35 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
// Map Structure: Rule Identifier -> Offspring Probability Of Passing Rule
|
|
|
|
// The map value stores the probability that the offspring will pass the rule
|
|
|
|
// This is a number between 0-100%
|
|
|
|
offspringProbabilityOfPassingRulesMap := make(map[[3]byte]int)
|
|
|
|
|
|
|
|
// Map Structure: Outcome Name -> Outcome Score
|
|
|
|
// Example: "Intolerant" -> 2.5
|
|
|
|
offspringAverageOutcomeScoresMap := make(map[string]float64)
|
|
|
|
|
|
|
|
for ruleIdentifier, ruleObject := range offspringRulesWithKnownStatusMap{
|
2024-04-11 15:51:56 +02:00
|
|
|
|
2024-06-05 06:10:35 +02:00
|
|
|
//Output:
|
|
|
|
// -int: Offspring probability of passing rule (0-100%)
|
|
|
|
getOffspringPercentageProbabilityOfPassingRule := func()int{
|
|
|
|
|
|
|
|
numberOfOffspringWhoPassRule, exists := offspringPassesRulesCountMap[ruleIdentifier]
|
|
|
|
if (exists == false){
|
|
|
|
// None of the offspring passed the rule
|
|
|
|
return 0
|
|
|
|
}
|
|
|
|
|
|
|
|
// There are 100 tested offspring
|
|
|
|
// Thus, the percentage of offspring who passed the rule is the same as the number of offspring who passed the rule
|
|
|
|
// The probability of the offspring passing the rule is the same as the percentage of offspring who passed the rule
|
|
|
|
|
|
|
|
return numberOfOffspringWhoPassRule
|
|
|
|
}
|
|
|
|
|
|
|
|
offspringPercentageProbabilityOfPassingRule := getOffspringPercentageProbabilityOfPassingRule()
|
|
|
|
|
|
|
|
offspringProbabilityOfPassingRulesMap[ruleIdentifier] = offspringPercentageProbabilityOfPassingRule
|
|
|
|
|
|
|
|
// This is the 0 - 1 probability value
|
|
|
|
offspringProbabilityOfPassingRule := float64(offspringPercentageProbabilityOfPassingRule)/100
|
|
|
|
|
|
|
|
ruleOutcomePointsMap := ruleObject.OutcomePointsMap
|
|
|
|
|
|
|
|
for outcomeName, outcomePointsEffect := range ruleOutcomePointsMap{
|
|
|
|
|
|
|
|
pointsToAdd := float64(outcomePointsEffect) * offspringProbabilityOfPassingRule
|
|
|
|
|
|
|
|
offspringAverageOutcomeScoresMap[outcomeName] += pointsToAdd
|
2024-04-11 15:51:56 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-06-05 06:10:35 +02:00
|
|
|
numberOfRulesTested := len(offspringProbabilityOfPassingRulesMap)
|
2024-04-11 15:51:56 +02:00
|
|
|
|
2024-06-05 06:10:35 +02:00
|
|
|
if (numberOfRulesTested == 0){
|
|
|
|
return false, 0, nil, nil, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
traitOutcomesList := traitObject.OutcomesList
|
2024-04-11 15:51:56 +02:00
|
|
|
|
2024-06-05 06:10:35 +02:00
|
|
|
// We add all outcomes for which there were no points
|
|
|
|
|
|
|
|
for _, traitOutcome := range traitOutcomesList{
|
|
|
|
|
2024-06-07 02:04:13 +02:00
|
|
|
_, exists := offspringAverageOutcomeScoresMap[traitOutcome]
|
|
|
|
if (exists == false){
|
|
|
|
offspringAverageOutcomeScoresMap[traitOutcome] = 0
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return true, numberOfRulesTested, offspringProbabilityOfPassingRulesMap, offspringAverageOutcomeScoresMap, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// This function will return a list of 100 prospective offspring genomes
|
|
|
|
// Each genome represents an equal-probability offspring genome from both people's genomes
|
|
|
|
// This function takes into account the effects of genetic linkage
|
|
|
|
// Any locations which do not exist in both people's genomes will not be included
|
|
|
|
//Outputs:
|
|
|
|
// -bool: Any locus value exists between both users
|
|
|
|
// -[]map[int64]locusValue.LocusValue
|
|
|
|
// -error
|
|
|
|
func getProspectiveOffspringGenomesList(lociList []int64, person1LociMap map[int64]locusValue.LocusValue, person2LociMap map[int64]locusValue.LocusValue)(bool, []map[int64]locusValue.LocusValue, error){
|
|
|
|
|
|
|
|
// -We use randomness to generate the offspring genomes
|
|
|
|
// -We want the results to be the same for each pair of people each time, so we have to seed our randomness generator
|
|
|
|
// -This is necessary so that two people's analysis results do not change every time
|
|
|
|
// -Instead, the same 2 people will produce the exact same result every time
|
|
|
|
pseudorandomNumberGenerator := mathRand.New(mathRand.NewPCG(1, 2))
|
|
|
|
|
|
|
|
//Outputs:
|
|
|
|
// -[]int64: A list of random breakpoints for this chromosome that are statistically accurate
|
|
|
|
// -error
|
|
|
|
getRandomChromosomeBreakpoints := func(chromosome int)([]int64, error){
|
|
|
|
|
|
|
|
getChromosomeLength := func()(int64, error){
|
|
|
|
|
|
|
|
// Approximate number of base pairs in each chromosome taken from: https://www.ncbi.nlm.nih.gov/books/NBK557784/
|
|
|
|
|
|
|
|
switch chromosome{
|
|
|
|
|
|
|
|
case 1:{
|
|
|
|
return 249000000, nil
|
|
|
|
}
|
|
|
|
case 2:{
|
|
|
|
return 243000000, nil
|
|
|
|
}
|
|
|
|
case 3:{
|
|
|
|
return 200000000, nil
|
|
|
|
}
|
|
|
|
case 4:{
|
|
|
|
return 192000000, nil
|
|
|
|
}
|
|
|
|
case 5:{
|
|
|
|
return 181000000, nil
|
|
|
|
}
|
|
|
|
case 6:{
|
|
|
|
return 170000000, nil
|
|
|
|
}
|
|
|
|
case 7:{
|
|
|
|
return 158000000, nil
|
|
|
|
}
|
|
|
|
case 8:{
|
|
|
|
return 146000000, nil
|
|
|
|
}
|
|
|
|
case 9:{
|
|
|
|
return 140000000, nil
|
|
|
|
}
|
|
|
|
case 10:{
|
|
|
|
return 135000000, nil
|
|
|
|
}
|
|
|
|
case 11:{
|
|
|
|
return 135000000, nil
|
|
|
|
}
|
|
|
|
case 12:{
|
|
|
|
return 132000000, nil
|
|
|
|
}
|
|
|
|
case 13:{
|
|
|
|
return 114000000, nil
|
|
|
|
}
|
|
|
|
case 14:{
|
|
|
|
return 106000000, nil
|
|
|
|
}
|
|
|
|
case 15:{
|
|
|
|
return 100000000, nil
|
|
|
|
}
|
|
|
|
case 16:{
|
|
|
|
return 89000000, nil
|
|
|
|
}
|
|
|
|
case 17:{
|
|
|
|
return 79000000, nil
|
|
|
|
}
|
|
|
|
case 18:{
|
|
|
|
return 76000000, nil
|
|
|
|
}
|
|
|
|
case 19:{
|
|
|
|
return 64000000, nil
|
|
|
|
}
|
|
|
|
case 20:{
|
|
|
|
return 62000000, nil
|
|
|
|
}
|
|
|
|
case 21:{
|
|
|
|
return 47000000, nil
|
|
|
|
}
|
|
|
|
case 22:{
|
|
|
|
return 50000000, nil
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
chromosomeString := helpers.ConvertIntToString(chromosome)
|
|
|
|
return 0, errors.New("getRandomChromosomeBreakpoints called with invalid chromosome: " + chromosomeString)
|
|
|
|
}
|
|
|
|
|
|
|
|
chromosomeLength, err := getChromosomeLength()
|
|
|
|
if (err != nil) { return nil, err }
|
|
|
|
|
|
|
|
listOfRandomBreakpoints := make([]int64, 0)
|
|
|
|
|
|
|
|
// TODO: Take into account different recombination rate for each chromosome
|
|
|
|
// TODO: There are also breakpoint hotspots which we need to account for
|
|
|
|
// TODO: I read somewhere that recombination break points are less likely to occur within genes,
|
|
|
|
// meaning they are more likely to occur at the gene boundaries (codons)
|
|
|
|
|
|
|
|
// We step by 1,000,000 each time
|
|
|
|
// It would be more realistic if we did it in 1 integer increments, but it would be slower
|
|
|
|
for position := int64(0); position <= chromosomeLength; position += 1000000{
|
|
|
|
|
|
|
|
//From Wikipedia:
|
|
|
|
// A centimorgan (abbreviated cM) is a unit for measuring genetic linkage.
|
|
|
|
// It is defined as the distance between chromosome positions (loci) for which the expected
|
|
|
|
// average number of intervening chromosomal crossovers in a single generation is 0.01.
|
|
|
|
// One centimorgan corresponds to about 1 million base pairs in humans on average
|
|
|
|
//
|
|
|
|
// A chromosomal crossover == recombination breakpoint
|
|
|
|
//
|
|
|
|
// For every 1,000,000 base pairs, there is a 0.01 probability that there is a breakpoint
|
|
|
|
|
|
|
|
randomFloat := pseudorandomNumberGenerator.Float64()
|
|
|
|
if (randomFloat <= 0.01){
|
|
|
|
// This has a 0.01, or 1% probability of being true
|
|
|
|
listOfRandomBreakpoints = append(listOfRandomBreakpoints, position)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return listOfRandomBreakpoints, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// Map Structure: rsID -> Locus Value
|
|
|
|
offspringGenomesList := make([]map[int64]locusValue.LocusValue, 0)
|
|
|
|
|
|
|
|
for i:=0; i < 100; i++{
|
|
|
|
|
|
|
|
// This map stores the chromosome breakpoints for person1
|
|
|
|
// Map Structure: Chromosome -> List of breakpoints
|
|
|
|
person1ChromosomeBreakpointsMap := make(map[int][]int64)
|
|
|
|
|
|
|
|
// This map stores the chromosome breakpoints for person2
|
|
|
|
// Map Structure: Chromosome -> List of breakpoints
|
|
|
|
person2ChromosomeBreakpointsMap := make(map[int][]int64)
|
|
|
|
|
|
|
|
// This stores the locus values for this prospective offspring
|
|
|
|
// Map Structure: rsID -> Locus Value
|
|
|
|
prospectiveOffspringGenome := make(map[int64]locusValue.LocusValue)
|
|
|
|
|
|
|
|
for _, rsID := range lociList{
|
|
|
|
|
|
|
|
//Outputs:
|
|
|
|
// -bool: Allele is known
|
|
|
|
// -string: Locus base
|
|
|
|
// -error
|
|
|
|
getPersonAllele := func(personLociMap map[int64]locusValue.LocusValue, personBreakpointsMap map[int][]int64)(bool, string, error){
|
|
|
|
|
|
|
|
personLocusValue, exists := personLociMap[rsID]
|
|
|
|
if (exists == false){
|
|
|
|
return false, "", nil
|
|
|
|
}
|
|
|
|
|
|
|
|
personLocusBase1 := personLocusValue.Base1Value
|
|
|
|
personLocusBase2 := personLocusValue.Base1Value
|
|
|
|
personLocusIsPhased := personLocusValue.LocusIsPhased
|
|
|
|
|
|
|
|
if (personLocusIsPhased == false){
|
|
|
|
// Breakpoints are unnecessary
|
|
|
|
// We either choose base 1 or 2
|
|
|
|
randomBool := helpers.GetRandomBool()
|
|
|
|
if (randomBool == true){
|
|
|
|
return true, personLocusBase1, nil
|
|
|
|
}
|
|
|
|
return true, personLocusBase2, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// We have a phased locus
|
|
|
|
// We figure out which allele to use by seeing which allele gets inherited from our random breakpoints list
|
|
|
|
// We figure out the chromosome and position of this locus
|
|
|
|
|
|
|
|
locusMetadataExists, locusMetadataObject, err := locusMetadata.GetLocusMetadata(rsID)
|
|
|
|
if (err != nil) { return false, "", err }
|
|
|
|
if (locusMetadataExists == false){
|
|
|
|
rsIDString := helpers.ConvertInt64ToString(rsID)
|
|
|
|
return false, "", errors.New("getProspectiveOffspringGenomesList called with unknown rsID: " + rsIDString)
|
|
|
|
}
|
|
|
|
|
|
|
|
locusPosition := locusMetadataObject.Position
|
|
|
|
locusChromosome := locusMetadataObject.Chromosome
|
|
|
|
|
|
|
|
getPersonChromosomeBreakpointsList := func()([]int64, error){
|
|
|
|
|
|
|
|
breakpointsList, exists := personBreakpointsMap[locusChromosome]
|
|
|
|
if (exists == true){
|
|
|
|
return breakpointsList, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// We have to create a new breakpoints list
|
|
|
|
|
|
|
|
newBreakpointsList, err := getRandomChromosomeBreakpoints(locusChromosome)
|
|
|
|
if (err != nil) { return nil, err }
|
|
|
|
|
|
|
|
personBreakpointsMap[locusChromosome] = newBreakpointsList
|
|
|
|
|
|
|
|
return newBreakpointsList, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
personBreakpointsList, err := getPersonChromosomeBreakpointsList()
|
|
|
|
if (err != nil) { return false, "", err }
|
|
|
|
|
|
|
|
getLocusListIndex := func()int{
|
|
|
|
|
|
|
|
for index, breakpoint := range personBreakpointsList{
|
|
|
|
|
|
|
|
if (int64(locusPosition) <= breakpoint){
|
|
|
|
|
|
|
|
return index
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
index := len(personBreakpointsList)
|
2024-06-05 06:10:35 +02:00
|
|
|
|
2024-06-07 02:04:13 +02:00
|
|
|
// This is reached if the final breakpoint in the list is less than the locus's position, or if there were no breakpoints
|
2024-04-11 15:51:56 +02:00
|
|
|
|
2024-06-07 02:04:13 +02:00
|
|
|
return index
|
|
|
|
}
|
2024-04-11 15:51:56 +02:00
|
|
|
|
2024-06-07 02:04:13 +02:00
|
|
|
locusListIndex := getLocusListIndex()
|
2024-04-11 15:51:56 +02:00
|
|
|
|
2024-06-07 02:04:13 +02:00
|
|
|
if (locusListIndex%2 == 0){
|
|
|
|
return true, personLocusBase1, nil
|
|
|
|
}
|
2024-04-11 15:51:56 +02:00
|
|
|
|
2024-06-07 02:04:13 +02:00
|
|
|
return true, personLocusBase2, nil
|
|
|
|
}
|
2024-04-11 15:51:56 +02:00
|
|
|
|
2024-06-07 02:04:13 +02:00
|
|
|
person1AlleleIsKnown, person1Allele, err := getPersonAllele(person1LociMap, person1ChromosomeBreakpointsMap)
|
|
|
|
if (err != nil) { return false, nil, err }
|
|
|
|
if (person1AlleleIsKnown == false){
|
|
|
|
continue
|
|
|
|
}
|
2024-04-11 15:51:56 +02:00
|
|
|
|
2024-06-07 02:04:13 +02:00
|
|
|
person2AlleleIsKnown, person2Allele, err := getPersonAllele(person2LociMap, person2ChromosomeBreakpointsMap)
|
|
|
|
if (err != nil) { return false, nil, err }
|
|
|
|
if (person2AlleleIsKnown == false){
|
|
|
|
continue
|
|
|
|
}
|
2024-04-11 15:51:56 +02:00
|
|
|
|
2024-06-07 02:04:13 +02:00
|
|
|
offspringLocusValue := locusValue.LocusValue{
|
|
|
|
Base1Value: person1Allele,
|
|
|
|
Base2Value: person2Allele,
|
|
|
|
LocusIsPhased: true,
|
2024-04-11 15:51:56 +02:00
|
|
|
}
|
2024-06-07 02:04:13 +02:00
|
|
|
|
|
|
|
prospectiveOffspringGenome[rsID] = offspringLocusValue
|
2024-04-11 15:51:56 +02:00
|
|
|
}
|
|
|
|
|
2024-06-07 02:04:13 +02:00
|
|
|
if (len(prospectiveOffspringGenome) == 0){
|
|
|
|
// We don't have any locations at which both people's genomes contain a locus value.
|
|
|
|
return false, nil, nil
|
|
|
|
}
|
2024-04-11 15:51:56 +02:00
|
|
|
|
2024-06-07 02:04:13 +02:00
|
|
|
offspringGenomesList = append(offspringGenomesList, prospectiveOffspringGenome)
|
2024-04-11 15:51:56 +02:00
|
|
|
}
|
|
|
|
|
2024-06-07 02:04:13 +02:00
|
|
|
return true, offspringGenomesList, nil
|
2024-04-11 15:51:56 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|