seekia/internal/genetics/createPersonGeneticAnalysis/createPersonGeneticAnalysis.go

1346 lines
48 KiB
Go

// createPersonGeneticAnalysis provides functions to create a Person genetic analysis
// These analyses are performed on one or more genome files.
// They contain 3 categories of results: Monogenic Diseases, Polygenic Diseases and Traits
// Use createCoupleGeneticAnalysis.go to create Couple genetic analyses
package createPersonGeneticAnalysis
// Disclaimer: I am a novice in the ways of genetics. This package could be flawed in numerous ways.
// 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"
import "seekia/internal/encoding"
import "seekia/internal/genetics/geneticAnalysis"
import "seekia/internal/genetics/geneticPrediction"
import "seekia/internal/genetics/locusValue"
import "seekia/internal/genetics/prepareRawGenomes"
import "seekia/internal/helpers"
import "errors"
import "slices"
import "reflect"
//Outputs:
// -bool: Process completed (it was not stopped manually before completion)
// -string: New Genetic analysis string (Encoded in MessagePack)
// -error
func CreatePersonGeneticAnalysis(genomesList []prepareRawGenomes.RawGenomeWithMetadata, updatePercentageCompleteFunction func(int)error, checkIfProcessIsStopped func()bool)(bool, string, error){
prepareRawGenomesUpdatePercentageCompleteFunction := func(newPercentage int)error{
newPercentageCompletion, err := helpers.ScaleIntProportionally(true, newPercentage, 0, 100, 0, 50)
if (err != nil){ return err }
err = updatePercentageCompleteFunction(newPercentageCompletion)
if (err != nil) { return err }
return nil
}
anyUsefulLocationsExist, genomesWithMetadataList, allRawGenomeIdentifiersList, multipleGenomesExist, onlyExcludeConflictsGenomeIdentifier, onlyIncludeSharedGenomeIdentifier, err := prepareRawGenomes.GetGenomesWithMetadataListFromRawGenomesList(genomesList, prepareRawGenomesUpdatePercentageCompleteFunction)
if (err != nil) { return false, "", err }
if (anyUsefulLocationsExist == false){
// We should have checked for this when genomes were first imported.
return false, "", errors.New("CreatePersonGeneticAnalysis called with genomeList containing no genomes with useful locations.")
}
if (len(genomesList) > 1 && (len(genomesList) != (len(genomesWithMetadataList)-2)) ){
// If there is more than 1 genome, 2 combined genomes are created
// We are checking to make sure that none of the input genomes were dropped due to not having any locations
// We should have checked to make sure each input genome has useful locations when each genome was first imported.
return false, "", errors.New("CreatePersonGeneticAnalysis called with genomeList containing at least 1 genome without useful locations.")
}
// This map stores each genome's locus values
// Map Structure: Genome Identifier -> Genome locus values map (rsID -> Locus Value)
genomesMap := make(map[[16]byte]map[int64]locusValue.LocusValue)
for _, genomeWithMetadata := range genomesWithMetadataList{
genomeIdentifier := genomeWithMetadata.GenomeIdentifier
genomeMap := genomeWithMetadata.GenomeMap
genomesMap[genomeIdentifier] = genomeMap
}
newGeneticAnalysisObject := geneticAnalysis.PersonAnalysis{
AnalysisVersion: 1,
CombinedGenomesExist: multipleGenomesExist,
AllRawGenomeIdentifiersList: allRawGenomeIdentifiersList,
GenomesMap: genomesMap,
}
if (multipleGenomesExist == true){
newGeneticAnalysisObject.OnlyExcludeConflictsGenomeIdentifier = onlyExcludeConflictsGenomeIdentifier
newGeneticAnalysisObject.OnlyIncludeSharedGenomeIdentifier = onlyIncludeSharedGenomeIdentifier
}
processIsStopped := checkIfProcessIsStopped()
if (processIsStopped == true){
return false, "", nil
}
monogenicDiseasesList, err := monogenicDiseases.GetMonogenicDiseaseObjectsList()
if (err != nil) { return false, "", err }
// Map Structure: Disease Name -> PersonMonogenicDiseaseInfo
analysisMonogenicDiseasesMap := make(map[string]geneticAnalysis.PersonMonogenicDiseaseInfo)
for _, monogenicDiseaseObject := range monogenicDiseasesList{
diseaseName := monogenicDiseaseObject.DiseaseName
personDiseaseAnalysisObject, err := GetPersonMonogenicDiseaseAnalysis(genomesWithMetadataList, monogenicDiseaseObject)
if (err != nil) { return false, "", err }
analysisMonogenicDiseasesMap[diseaseName] = personDiseaseAnalysisObject
}
newGeneticAnalysisObject.MonogenicDiseasesMap = analysisMonogenicDiseasesMap
polygenicDiseaseObjectsList, err := polygenicDiseases.GetPolygenicDiseaseObjectsList()
if (err != nil) { return false, "", err }
// Map Structure: Disease Name -> PersonPolygenicDiseaseInfo
analysisPolygenicDiseasesMap := make(map[string]geneticAnalysis.PersonPolygenicDiseaseInfo)
for _, diseaseObject := range polygenicDiseaseObjectsList{
personDiseaseAnalysisObject, err := GetPersonPolygenicDiseaseAnalysis(genomesWithMetadataList, diseaseObject)
if (err != nil) { return false, "", err }
diseaseName := diseaseObject.DiseaseName
analysisPolygenicDiseasesMap[diseaseName] = personDiseaseAnalysisObject
}
newGeneticAnalysisObject.PolygenicDiseasesMap = analysisPolygenicDiseasesMap
traitObjectsList, err := traits.GetTraitObjectsList()
if (err != nil) { return false, "", err }
// This map will always contain an entry for each discrete trait
// Map Structure: Trait Name -> PersonDiscreteTraitInfo
analysisDiscreteTraitsMap := make(map[string]geneticAnalysis.PersonDiscreteTraitInfo)
// This map will not contain entries for traits which this person's genome has no known loci
// Map Structure: Trait Name -> PersonNumericTraitInfo
analysisNumericTraitsMap := make(map[string]geneticAnalysis.PersonNumericTraitInfo)
for _, traitObject := range traitObjectsList{
traitName := traitObject.TraitName
traitIsDiscreteOrNumeric := traitObject.DiscreteOrNumeric
if (traitIsDiscreteOrNumeric == "Discrete"){
personTraitAnalysisObject, err := GetPersonDiscreteTraitAnalysis(genomesWithMetadataList, traitObject)
if (err != nil) { return false, "", err }
analysisDiscreteTraitsMap[traitName] = personTraitAnalysisObject
} else {
//traitIsDiscreteOrNumeric == "Numeric"
personTraitAnalysisObject, err := GetPersonNumericTraitAnalysis(genomesWithMetadataList, traitObject)
if (err != nil) { return false, "", err }
analysisNumericTraitsMap[traitName] = personTraitAnalysisObject
}
}
newGeneticAnalysisObject.DiscreteTraitsMap = analysisDiscreteTraitsMap
newGeneticAnalysisObject.NumericTraitsMap = analysisNumericTraitsMap
analysisBytes, err := encoding.EncodeMessagePackBytes(newGeneticAnalysisObject)
if (err != nil) { return false, "", err }
analysisString := string(analysisBytes)
return true, analysisString, nil
}
//Outputs:
// -geneticAnalysis.PersonMonogenicDiseaseInfo: Monogenic disease analysis object
// -error
func GetPersonMonogenicDiseaseAnalysis(inputGenomesWithMetadataList []prepareRawGenomes.GenomeWithMetadata, diseaseObject monogenicDiseases.MonogenicDisease)(geneticAnalysis.PersonMonogenicDiseaseInfo, error){
emptyDiseaseInfoObject := geneticAnalysis.PersonMonogenicDiseaseInfo{}
dominantOrRecessive := diseaseObject.DominantOrRecessive
variantsList := diseaseObject.VariantsList
// We use this map to keep track of which RSIDs corresponds to each variant
// We also use it to have a map of all variants for the disease
// Map Structure: Variant Identifier -> []rsID
variantRSIDsMap := make(map[[3]byte][]int64)
// 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{})
for _, variantObject := range variantsList{
variantIdentifierHex := variantObject.VariantIdentifier
variantIdentifier, err := encoding.DecodeHexStringTo3ByteArray(variantIdentifierHex)
if (err != nil) { return emptyDiseaseInfoObject, err }
variantRSID := variantObject.VariantRSID
variantRSIDsList := []int64{variantRSID}
// We add aliases to variantRSIDsList
anyAliasesExist, rsidAliasesList, err := locusMetadata.GetRSIDAliases(variantRSID)
if (err != nil) { return emptyDiseaseInfoObject, err }
if (anyAliasesExist == true){
variantRSIDsList = append(variantRSIDsList, rsidAliasesList...)
}
variantRSIDsMap[variantIdentifier] = variantRSIDsList
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
}
// 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
basePairValueFound, base1Value, base2Value, locusIsPhased, _, err := GetLocusValueFromGenomeMap(true, genomeMap, variantRSID)
if (err != nil) { return emptyDiseaseInfoObject, err }
if (basePairValueFound == false){
// This genome does not contain info for this variant
// We skip it
continue
}
// This genome has at least 1 variant
variantDefectiveBase := variantObject.DefectiveBase
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)
newDiseaseVariantInfoObject := geneticAnalysis.PersonGenomeMonogenicDiseaseVariantInfo{
Base1HasVariant: base1IsDefective,
Base2HasVariant: base2IsDefective,
LocusIsPhased: locusIsPhased,
}
variantsInfoMap[variantIdentifier] = newDiseaseVariantInfoObject
//TODO: Add LocusIsPhased to readGeneticAnalysis package
}
// 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
numberOfVariantsTested := len(variantsInfoMap)
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
}
// This stores the number of loci that were tested
// Each locus can have multiple potential variants
numberOfLociTested := 0
// 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
for rsID, _ := range allUniqueRSIDsMap{
locusValueExists, _, _, locusIsPhased, _, err := GetLocusValueFromGenomeMap(true, genomeMap, rsID)
if (err != nil) { return emptyDiseaseInfoObject, err }
if (locusValueExists == false){
continue
}
numberOfLociTested += 1
if (locusIsPhased == true){
numberOfPhasedLoci += 1
}
}
// Outputs:
// -bool: Person has disease
// -float64: Probability Person will pass a defect (variant) to offspring (0-1)
// -error
getPersonDiseaseInfo := func()(bool, float64, error){
// 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
// 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
// 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)
for variantIdentifier, variantInfoObject := range variantsInfoMap{
locusIsPhasedStatus := variantInfoObject.LocusIsPhased
base1HasVariant := variantInfoObject.Base1HasVariant
base2HasVariant := variantInfoObject.Base2HasVariant
if (base1HasVariant == false && base2HasVariant == false){
// Neither chromosome contains the variant mutation.
continue
}
if (base1HasVariant == true && base2HasVariant == true){
// Both chromosomes contain the same variant mutation.
// Person has the disease.
// Person will definitely pass disease variant to offspring.
return true, 1, nil
}
// We know that this variant exists on 1 of the bases, but not both.
variantRSIDsList, exists := variantRSIDsMap[variantIdentifier]
if (exists == false){
return false, 0, errors.New("variantRSIDsMap missing variantIdentifier.")
}
for _, rsID := range variantRSIDsList{
rsidMutationsMap[rsID] += 1
}
if (locusIsPhasedStatus == true){
if (base1HasVariant == true){
numberOfVariants_Chromosome1 += 1
}
if (base2HasVariant == true){
numberOfVariants_Chromosome2 += 1
}
} else {
if (base1HasVariant == true || base2HasVariant == true){
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
return false, 0, nil
}
// 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
return true, 1, nil
}
}
// At this point, we know that there are no homozygous variant mutations
// All variant mutations are heterozygous, meaning the other chromosome strand's base is healthy
//Outputs:
// -bool: Person has disease
getPersonHasDiseaseBool := func()bool{
if (dominantOrRecessive == "Dominant"){
// Only 1 variant is needed for the person to have the disease
// We know they have at least 1 variant
return true
}
// 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
return false
}
// We know that there are at least 2 variants
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
return true
}
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
return false
}
// We know there are at least 2 variants
// We know there is at least 1 variant whose phase is unknown
// If all mutations are on the same chromosome, the person does not have the disease.
// If at least 1 mutation exists on each chromosome, the person does have the disease.
// 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
return false
}
personHasDiseaseBool := getPersonHasDiseaseBool()
// Output:
// -float64: Probability person will pass a disease variant to their offspring (0-1)
getPersonWillPassVariantProbability := func()float64{
if (totalNumberOfVariants == 1){
// There is only 1 variant on any chromosome
// The probability of the person passing a variant is 50%.
return 0.5
}
// We know that there are at least 2 variants
if (numberOfVariants_Chromosome1 >= 1 && numberOfVariants_Chromosome2 >= 1){
// We know there is at least 1 variant mutation on each chromosome
// Therefore, the person will definitely pass a variant
return 1
}
if (numberOfVariants_UnknownChromosome == 0){
// We know that variants do not exist on both chromosomes, only on 1.
// Thus, the person has a 50% probability of passing a variant
return 0.5
}
// We know all variants are heterozygous
// From Wikipeia:
// The human genome contains somewhere between 19,000 and 20,000 protein-coding genes.
// These genes contain an average of 10 introns and the average size of an intron is about 6 kb (6,000 base pairs)
// This means that the average size of a protein-coding gene is about 62 kb (62,000 base pairs)
// The probability of a recombination breakpoint occurring within the gene is very small
// If there is 1 breakpoint every 100 million locations, on average, and each gene is 62,000 base pairs long,
// then the probability of a breakpoint occurring within a gene is 62,000/100,000,000 = 0.00062 = .062%
// Thus, we disregard the risk of a breakpoint occurring within a gene
// I also read somewhere that breakpoints are less likely to occurr within genes, which makes this likelihood even smaller
// At this point, we know there at at least 2 variants
// We know that at least 1 of the variants has an unknown phase
// We don't know if all of the variants belong to the same chromosome
// If variants exist on both chromosomes, then the probability of passing a variant is 100%
// If all variants exist on the same chromosome, then the probability of passing a variant is 50%
// We know there is at least a 50% chance of passing a variant, and possibly higher
return 0.5
}
personWillPassVariantProbability := getPersonWillPassVariantProbability()
return personHasDiseaseBool, personWillPassVariantProbability, nil
}
personHasDisease, probabilityPersonWillPassAnyVariant, err := getPersonDiseaseInfo()
if (err != nil) { return emptyDiseaseInfoObject, err }
percentageProbabilityPersonWillPassADiseaseVariant := int(probabilityPersonWillPassAnyVariant * 100)
diseaseAnalysisObject := geneticAnalysis.PersonGenomeMonogenicDiseaseInfo{
PersonHasDisease: personHasDisease,
QuantityOfVariantsTested: numberOfVariantsTested,
QuantityOfLociTested: numberOfLociTested,
QuantityOfPhasedLoci: numberOfPhasedLoci,
ProbabilityOfPassingADiseaseVariant: percentageProbabilityPersonWillPassADiseaseVariant,
VariantsInfoMap: variantsInfoMap,
}
monogenicDiseaseInfoMap[genomeIdentifier] = diseaseAnalysisObject
}
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
// Nothing left to do. Analysis is complete.
return personMonogenicDiseaseInfoObject, nil
}
// We check for conflicts
getConflictExistsBool := func()(bool, error){
firstItemReached := false
personHasDisease := false
probabilityOfPassingAVariant := 0
for _, currentGenomeDiseaseAnalysisObject := range monogenicDiseaseInfoMap{
currentGenomePersonHasDisease := currentGenomeDiseaseAnalysisObject.PersonHasDisease
currentGenomeProbabilityOfPassingAVariant := currentGenomeDiseaseAnalysisObject.ProbabilityOfPassingADiseaseVariant
if (firstItemReached == false){
personHasDisease = currentGenomePersonHasDisease
probabilityOfPassingAVariant = currentGenomeProbabilityOfPassingAVariant
firstItemReached = true
continue
}
if (currentGenomePersonHasDisease != personHasDisease){
return true, nil
}
if (currentGenomeProbabilityOfPassingAVariant != probabilityOfPassingAVariant){
return true, nil
}
}
// Now we test variants for conflicts
// 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
for variantIdentifier, _ := range variantRSIDsMap{
// Each variant base pair is either true/false, true/true, false/false, false/true
// 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
// 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
// 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
firstItemReached := false
base1HasVariant := false
base2HasVariant := false
for _, currentGenomeDiseaseAnalysisObject := range monogenicDiseaseInfoMap{
variantsInfoMap := currentGenomeDiseaseAnalysisObject.VariantsInfoMap
variantInfoObject, exists := variantsInfoMap[variantIdentifier]
if (exists == false){
if (firstItemReached == true){
// A previous genome has information for this variant, and the current one does not
return true, nil
}
continue
}
currentBase1HasVariant := variantInfoObject.Base1HasVariant
currentBase2HasVariant := variantInfoObject.Base2HasVariant
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
}
// A conflict exists
return true, nil
}
}
return false, nil
}
conflictExists, err := getConflictExistsBool()
if (err != nil) { return emptyDiseaseInfoObject, err }
personMonogenicDiseaseInfoObject.ConflictExists = conflictExists
return personMonogenicDiseaseInfoObject, nil
}
//Outputs:
// -geneticAnalysis.PersonPolygenicDiseaseInfo
// -error
func GetPersonPolygenicDiseaseAnalysis(inputGenomesWithMetadataList []prepareRawGenomes.GenomeWithMetadata, diseaseObject polygenicDiseases.PolygenicDisease)(geneticAnalysis.PersonPolygenicDiseaseInfo, error){
// We use this when returning errors
emptyDiseaseInfoObject := geneticAnalysis.PersonPolygenicDiseaseInfo{}
// 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)
// We construct polygenic disease probability info for each genome
for _, genomeWithMetadataObject := range inputGenomesWithMetadataList{
genomeIdentifier := genomeWithMetadataObject.GenomeIdentifier
genomeMap := genomeWithMetadataObject.GenomeMap
neuralNetworkExists, anyLociTested, personDiseaseRiskScore, predictionAccuracyRangesMap, genomeQuantityOfLociKnown, genomeQuantityOfPhasedLoci, err := GetPersonGenomePolygenicDiseaseAnalysis(diseaseObject, genomeMap, true)
if (err != nil) { return emptyDiseaseInfoObject, err }
if (neuralNetworkExists == false || anyLociTested == false){
continue
}
newDiseaseInfoObject := geneticAnalysis.PersonGenomePolygenicDiseaseInfo{
RiskScore: personDiseaseRiskScore,
ConfidenceRangesMap: predictionAccuracyRangesMap,
QuantityOfLociKnown: genomeQuantityOfLociKnown,
QuantityOfPhasedLoci: genomeQuantityOfPhasedLoci,
}
personPolygenicDiseaseInfoMap[genomeIdentifier] = newDiseaseInfoObject
}
newPersonPolygenicDiseaseInfoObject := geneticAnalysis.PersonPolygenicDiseaseInfo{
PolygenicDiseaseInfoMap: personPolygenicDiseaseInfoMap,
}
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
}
// We check for conflicts between the different genome's results
getConflictExistsBool := func()(bool, error){
// First we check to see if any of the genomes have different risk scores or NumberOfLociTested
personGenomePolygenicDiseaseInfoObject := geneticAnalysis.PersonGenomePolygenicDiseaseInfo{}
firstItemReached := false
for _, personGenomeDiseaseInfoObject := range personPolygenicDiseaseInfoMap{
if (firstItemReached == false){
personGenomePolygenicDiseaseInfoObject = personGenomeDiseaseInfoObject
firstItemReached = true
continue
}
areEqual := reflect.DeepEqual(personGenomeDiseaseInfoObject, personGenomePolygenicDiseaseInfoObject)
if (areEqual == false){
return true, nil
}
}
return false, nil
}
conflictExists, err := getConflictExistsBool()
if (err != nil) { return emptyDiseaseInfoObject, err }
newPersonPolygenicDiseaseInfoObject.ConflictExists = conflictExists
return newPersonPolygenicDiseaseInfoObject, nil
}
//Outputs:
// -bool: Neural network exists for disease
// -bool: Any loci tested
// -int: Person genome risk score (value between 0-10)
// -map[int]float64: Confidence ranges map
// -If we want to know how accurate the prediction is with a X% accuracy, how far would we have to expand the
// risk score's range to be accurate, X% of the time?
// -Map Structure: Percentage -> Distance to travel in both directions of prediction
// -int: Person Genome quantity of loci known
// -int: Person genome quantity of phased loci
// -error
func GetPersonGenomePolygenicDiseaseAnalysis(diseaseObject polygenicDiseases.PolygenicDisease, personGenomeMap map[int64]locusValue.LocusValue, checkForAliases bool)(bool, bool, int, map[int]float64, int, int, error){
diseaseLociList := diseaseObject.LociList
getGenomeLocusValuesMap := func()(map[int64]locusValue.LocusValue, error){
if (checkForAliases == false){
// We don't need to check for rsID aliases.
return personGenomeMap, nil
}
// This map contains the locus values for the genome
// If a locus's entry doesn't exist, its value is unknown
// Map Structure: Locus rsID -> Locus Value
genomeLocusValuesMap := make(map[int64]locusValue.LocusValue)
for _, locusRSID := range diseaseLociList{
locusBasePairKnown, _, _, _, locusValueObject, err := GetLocusValueFromGenomeMap(checkForAliases, personGenomeMap, locusRSID)
if (err != nil) { return nil, err }
if (locusBasePairKnown == false){
continue
}
genomeLocusValuesMap[locusRSID] = locusValueObject
}
return genomeLocusValuesMap, nil
}
genomeLocusValuesMap, err := getGenomeLocusValuesMap()
if (err != nil) { return false, false, 0, nil, 0, 0, err }
diseaseName := diseaseObject.DiseaseName
neuralNetworkModelExists, riskScorePredictionIsPossible, predictedRiskScore, predictionAccuracyRangesMap, quantityOfLociKnown, quantityOfPhasedLoci, err := geneticPrediction.GetNeuralNetworkNumericAttributePredictionFromGenomeMap(diseaseName, diseaseLociList, genomeLocusValuesMap)
if (err != nil) { return false, false, 0, nil, 0, 0, err }
if (neuralNetworkModelExists == false){
return false, false, 0, nil, 0, 0, nil
}
if (riskScorePredictionIsPossible == false){
return true, false, 0, nil, 0, 0, nil
}
predictedRiskScoreInt := int(predictedRiskScore)
return true, true, predictedRiskScoreInt, predictionAccuracyRangesMap, quantityOfLociKnown, quantityOfPhasedLoci, nil
}
//Outputs:
// -geneticAnalysis.PersonDiscreteTraitInfo: Trait analysis object
// -error
func GetPersonDiscreteTraitAnalysis(inputGenomesWithMetadataList []prepareRawGenomes.GenomeWithMetadata, traitObject traits.Trait)(geneticAnalysis.PersonDiscreteTraitInfo, error){
// Map Structure: Genome Identifier -> PersonGenomeDiscreteTraitInfo
newPersonTraitInfoMap := make(map[[16]byte]geneticAnalysis.PersonGenomeDiscreteTraitInfo)
for _, genomeWithMetadataObject := range inputGenomesWithMetadataList{
genomeIdentifier := genomeWithMetadataObject.GenomeIdentifier
genomeMap := genomeWithMetadataObject.GenomeMap
newPersonGenomeTraitInfo := geneticAnalysis.PersonGenomeDiscreteTraitInfo{}
neuralNetworkExists, neuralNetworkOutcomeIsKnown, predictedOutcome, predictionConfidence, quantityOfLociTested, quantityOfPhasedLoci, err := GetGenomeDiscreteTraitAnalysis_NeuralNetwork(traitObject, genomeMap, true)
if (err != nil) { return geneticAnalysis.PersonDiscreteTraitInfo{}, err }
if (neuralNetworkExists == true){
newPersonGenomeTraitInfo.NeuralNetworkExists = true
if (neuralNetworkOutcomeIsKnown == true){
newPersonGenomeTraitInfo.NeuralNetworkAnalysisExists = true
newPersonGenomeTraitInfo_NeuralNetwork := geneticAnalysis.PersonGenomeDiscreteTraitInfo_NeuralNetwork{
PredictedOutcome: predictedOutcome,
PredictionConfidence: predictionConfidence,
QuantityOfLociKnown: quantityOfLociTested,
QuantityOfPhasedLoci: quantityOfPhasedLoci,
}
newPersonGenomeTraitInfo.NeuralNetworkAnalysis = newPersonGenomeTraitInfo_NeuralNetwork
}
}
anyRulesExist, quantityOfRulesTested, quantityOfLociKnown, genomePassesRulesMap, predictedOutcomeExists, predictedOutcome, err := GetGenomeDiscreteTraitAnalysis_Rules(traitObject, genomeMap, true)
if (err != nil) { return geneticAnalysis.PersonDiscreteTraitInfo{}, err }
if (anyRulesExist == true){
newPersonGenomeTraitInfo.AnyRulesExist = true
if (quantityOfRulesTested != 0){
newPersonGenomeTraitInfo.RulesAnalysisExists = true
newPersonGenomeTraitInfo_Rules := geneticAnalysis.PersonGenomeDiscreteTraitInfo_Rules{
GenomePassesRulesMap: genomePassesRulesMap,
PredictedOutcomeExists: predictedOutcomeExists,
PredictedOutcome: predictedOutcome,
QuantityOfRulesTested: quantityOfRulesTested,
QuantityOfLociKnown: quantityOfLociKnown,
}
newPersonGenomeTraitInfo.RulesAnalysis = newPersonGenomeTraitInfo_Rules
}
}
newPersonTraitInfoMap[genomeIdentifier] = newPersonGenomeTraitInfo
}
newPersonTraitInfoObject := geneticAnalysis.PersonDiscreteTraitInfo{
TraitInfoMap: newPersonTraitInfoMap,
}
if (len(newPersonTraitInfoMap) <= 1){
// We do not need to check for conflicts, there is only <=1 genome with trait information
// Nothing left to do. Analysis is complete.
return newPersonTraitInfoObject, nil
}
// We check for conflicts
getConflictExistsBool := func()(bool, error){
// We check to see if the analysis results are the same for all genomes
firstItemReached := false
personGenomeTraitInfoObject := geneticAnalysis.PersonGenomeDiscreteTraitInfo{}
for _, genomeTraitInfoObject := range newPersonTraitInfoMap{
if (firstItemReached == false){
personGenomeTraitInfoObject = genomeTraitInfoObject
continue
}
areEqual := reflect.DeepEqual(personGenomeTraitInfoObject, genomeTraitInfoObject)
if (areEqual == false){
return true, nil
}
}
return false, nil
}
conflictExists, err := getConflictExistsBool()
if (err != nil) { return geneticAnalysis.PersonDiscreteTraitInfo{}, err }
newPersonTraitInfoObject.ConflictExists = conflictExists
return newPersonTraitInfoObject, nil
}
//Outputs:
// -geneticAnalysis.PersonNumericTraitInfo: Trait analysis object
// -error
func GetPersonNumericTraitAnalysis(inputGenomesWithMetadataList []prepareRawGenomes.GenomeWithMetadata, traitObject traits.Trait)(geneticAnalysis.PersonNumericTraitInfo, error){
// Map Structure: Genome Identifier -> PersonGenomeNumericTraitInfo
newPersonTraitInfoMap := make(map[[16]byte]geneticAnalysis.PersonGenomeNumericTraitInfo)
for _, genomeWithMetadataObject := range inputGenomesWithMetadataList{
genomeIdentifier := genomeWithMetadataObject.GenomeIdentifier
genomeMap := genomeWithMetadataObject.GenomeMap
neuralNetworkExists, neuralNetworkOutcomeIsKnown, predictedOutcome, predictionConfidenceRangesMap, quantityOfLociKnown, quantityOfPhasedLoci, err := GetGenomeNumericTraitAnalysis(traitObject, genomeMap, true)
if (err != nil) { return geneticAnalysis.PersonNumericTraitInfo{}, err }
if (neuralNetworkExists == false || neuralNetworkOutcomeIsKnown == false){
continue
}
newPersonGenomeTraitInfo := geneticAnalysis.PersonGenomeNumericTraitInfo{
PredictedOutcome: predictedOutcome,
ConfidenceRangesMap: predictionConfidenceRangesMap,
QuantityOfLociKnown: quantityOfLociKnown,
QuantityOfPhasedLoci: quantityOfPhasedLoci,
}
newPersonTraitInfoMap[genomeIdentifier] = newPersonGenomeTraitInfo
}
newPersonTraitInfoObject := geneticAnalysis.PersonNumericTraitInfo{
TraitInfoMap: newPersonTraitInfoMap,
}
if (len(newPersonTraitInfoMap) <= 1){
// We do not need to check for conflicts, there is only <=1 genome with trait information
// Nothing left to do. Analysis is complete.
return newPersonTraitInfoObject, nil
}
// We check for conflicts
getConflictExistsBool := func()(bool, error){
// We check to see if the analysis results are the same for all genomes
firstItemReached := false
personGenomeTraitInfoObject := geneticAnalysis.PersonGenomeNumericTraitInfo{}
for _, genomeTraitInfoObject := range newPersonTraitInfoMap{
if (firstItemReached == false){
personGenomeTraitInfoObject = genomeTraitInfoObject
continue
}
areEqual := reflect.DeepEqual(personGenomeTraitInfoObject, genomeTraitInfoObject)
if (areEqual == false){
return true, nil
}
}
return false, nil
}
conflictExists, err := getConflictExistsBool()
if (err != nil) { return geneticAnalysis.PersonNumericTraitInfo{}, err }
newPersonTraitInfoObject.ConflictExists = conflictExists
return newPersonTraitInfoObject, nil
}
// We use this to generate discrete trait predictions using a neural network
// The alternative prediction method is to use Rules (see GetGenomeTraitAnalysis_Rules)
//Outputs:
// -bool: Trait Neural network analysis available (if false, we can't predict this trait using a neural network)
// -bool: Neural network outcome is known (at least 1 locus value is known which is needed for the neural network
// -string: The predicted outcome (Example: "Blue")
// -int: Probability (0-100) that the outcome from neural network is true (confidence)
// -int: Quantity of loci tested
// -int: Quantity of phased loci
// -error
func GetGenomeDiscreteTraitAnalysis_NeuralNetwork(traitObject traits.Trait, genomeMap map[int64]locusValue.LocusValue, checkForAliases bool)(bool, bool, string, int, int, int, error){
getGenomeLocusValuesMap := func()(map[int64]locusValue.LocusValue, error){
if (checkForAliases == false){
// We don't need to check for rsID aliases.
return genomeMap, nil
}
traitLociList := traitObject.LociList
// This map contains the locus values for the genome
// If a locus's entry doesn't exist, its value is unknown
// Map Structure: Locus rsID -> Locus Value
genomeLocusValuesMap := make(map[int64]locusValue.LocusValue)
for _, locusRSID := range traitLociList{
locusBasePairKnown, _, _, _, locusValueObject, err := GetLocusValueFromGenomeMap(checkForAliases, genomeMap, locusRSID)
if (err != nil) { return nil, err }
if (locusBasePairKnown == false){
continue
}
genomeLocusValuesMap[locusRSID] = locusValueObject
}
return genomeLocusValuesMap, nil
}
genomeLocusValuesMap, err := getGenomeLocusValuesMap()
if (err != nil) { return false, false, "", 0, 0, 0, err }
traitName := traitObject.TraitName
neuralNetworkModelExists, traitPredictionIsPossible, predictedOutcome, predictionConfidence, quantityOfLociKnown, quantityOfPhasedLoci, err := geneticPrediction.GetNeuralNetworkDiscreteTraitPredictionFromGenomeMap(traitName, genomeLocusValuesMap)
if (err != nil) { return false, false, "", 0, 0, 0, err }
if (neuralNetworkModelExists == false){
return false, false, "", 0, 0, 0, nil
}
if (traitPredictionIsPossible == false){
return true, false, "", 0, 0, 0, nil
}
return true, true, predictedOutcome, predictionConfidence, quantityOfLociKnown, quantityOfPhasedLoci, nil
}
//Outputs:
// -bool: Rule-based trait prediction is available
// -int: Quantity of trait rules tested
// -int: Quantity of loci known
// -map[[3]byte]bool: Passed rules map (Rule Identifier -> Genome passes rule)
// -bool: Rule-based prediction outcome is known (at least 1 rule has been tested and there is no outcome tie)
// -string: The predicted outcome (Example: "Tolerant")
// -error
func GetGenomeDiscreteTraitAnalysis_Rules(traitObject traits.Trait, genomeMap map[int64]locusValue.LocusValue, checkForAliases bool)(bool, int, int, map[[3]byte]bool, bool, string, error){
traitIsDiscreteOrNumeric := traitObject.DiscreteOrNumeric
if (traitIsDiscreteOrNumeric != "Discrete"){
return false, 0, 0, nil, false, "", errors.New("GetGenomeDiscreteTraitAnalysis_Rules called with non-discrete trait.")
}
traitRulesList := traitObject.RulesList
if (len(traitRulesList) == 0){
// We can't predict this trait using rules
// This means that neural network prediction is the only alternative potential way to predict this trait
return false, 0, 0, nil, false, "", nil
}
// This map contains the trait outcome scores for the genome
// Map Structure: Outcome Name -> Score
// Example: "Intolerant" -> 5
traitOutcomeScoresMap := make(map[string]int)
// Map Structure: Rule Identifier -> Genome Passes rule (true if the genome passes the rule)
personPassesRulesMap := make(map[[3]byte]bool)
// This map stores each known loci
// Multiple rules can use the same loci, so we need a map to avoid duplicates
// Map Structure: Locus RSID -> Locus is known
lociAreKnownMap := make(map[int64]bool)
for _, ruleObject := range traitRulesList{
ruleIdentifierHex := ruleObject.RuleIdentifier
ruleIdentifier, err := encoding.DecodeHexStringTo3ByteArray(ruleIdentifierHex)
if (err != nil) { return false, 0, 0, nil, false, "", err }
ruleLociList := ruleObject.LociList
for _, locusObject := range ruleLociList{
locusRSID := locusObject.LocusRSID
_, exists := lociAreKnownMap[locusRSID]
if (exists == true){
// We already know if this locus is known
continue
}
locusIsKnown, _, _, _, _, err := GetLocusValueFromGenomeMap(checkForAliases, genomeMap, locusRSID)
if (err != nil) { return false, 0, 0, nil, false, "", err }
lociAreKnownMap[locusRSID] = locusIsKnown
}
genomePassesRuleIsKnown, genomePassesRule, err := GetGenomePassesDiscreteTraitRuleStatus(ruleLociList, genomeMap, checkForAliases)
if (err != nil) { return false, 0, 0, nil, false, "", err }
if (genomePassesRuleIsKnown == false){
continue
}
personPassesRulesMap[ruleIdentifier] = genomePassesRule
// The rule has been passed by this genome
// We add the outcome points for the rule to the traitOutcomeScoresMap
ruleOutcomePointsMap := ruleObject.OutcomePointsMap
for traitOutcome, pointsChange := range ruleOutcomePointsMap{
traitOutcomeScoresMap[traitOutcome] += pointsChange
}
}
quantityOfLociKnown := 0
for _, locusIsKnown := range lociAreKnownMap{
if (locusIsKnown == true){
quantityOfLociKnown += 1
}
}
quantityOfRulesTested := len(personPassesRulesMap)
if (quantityOfRulesTested == 0){
return true, 0, quantityOfLociKnown, personPassesRulesMap, false, "", nil
}
// -bool: Outcome is known (will be false if there is a tie
// -string:
// -error
getOutcome := func()(bool, string, error){
largestOutcome := ""
largestOutcomePoints := 0
tieExists := false
for outcomeName, outcomePoints := range traitOutcomeScoresMap{
if (outcomePoints < 1){
// This should never happen, because outcomes points should only be increased by integers which are at least 1 or greater
return false, "", errors.New("traitOutcomeScoresMap contains outcomePoints < 1.")
}
if (outcomePoints > largestOutcomePoints){
largestOutcome = outcomeName
largestOutcomePoints = outcomePoints
tieExists = false
continue
} else if (outcomePoints == largestOutcomePoints){
tieExists = true
}
}
if (tieExists == true){
return false, "", nil
}
return true, largestOutcome, nil
}
outcomeIsKnown, outcomeName, err := getOutcome()
if (err != nil) { return false, 0, 0, nil, false, "", err }
if (outcomeIsKnown == false){
return true, quantityOfRulesTested, quantityOfLociKnown, personPassesRulesMap, false, "", nil
}
return true, quantityOfRulesTested, quantityOfLociKnown, personPassesRulesMap, true, outcomeName, nil
}
// This function checks to see if a genome will pass a trait rule
// Outputs:
// -bool: Genome passes trait rule status is known
// -bool: Genome passes trait rule
// -error
func GetGenomePassesDiscreteTraitRuleStatus(ruleLociList []traits.RuleLocus, genomeMap map[int64]locusValue.LocusValue, checkForAliases bool)(bool, bool, error){
// We check to see if genome passes all rule loci
// To pass a rule, all of the rule's loci must be passed by the provided genome
// 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 there are no rules which are known not to be passed
anyLocusIsUnknown := false
for _, locusObject := range ruleLociList{
locusRSID := locusObject.LocusRSID
locusBasePairKnown, locusBase1, locusBase2, _, _, err := GetLocusValueFromGenomeMap(checkForAliases, genomeMap, locusRSID)
if (err != nil) { return false, false, err }
if (locusBasePairKnown == false){
anyLocusIsUnknown = true
// We keep searching to see if any of the rule's loci are known to not pass
continue
}
locusBasePairJoined := locusBase1 + ";" + locusBase2
locusBasePairsList := locusObject.BasePairsList
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
}
}
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
}
// All rules were passed
return true, true, nil
}
// We use this to generate numeric trait predictions using a neural network
//Outputs:
// -bool: Trait Neural network analysis available (if false, we can't predict this trait using a neural network)
// -bool: Neural network outcome is known (at least 1 locus value is known which is needed for the neural network
// -float64: The predicted value (Example: Height in centimeters)
// -map[int]float64: Accuracy ranges map
// -Map Structure: Probability prediction is accurate (X) -> Distance from predictoin that must be travelled in both directions to
// create a range in which the true value will fall into, X% of the time
// -int: Quantity of loci known
// -int: Quantity of phased loci
// -error
func GetGenomeNumericTraitAnalysis(traitObject traits.Trait, genomeMap map[int64]locusValue.LocusValue, checkForAliases bool)(bool, bool, float64, map[int]float64, int, int, error){
traitLociList := traitObject.LociList
getGenomeLocusValuesMap := func()(map[int64]locusValue.LocusValue, error){
if (checkForAliases == false){
// We don't need to check for rsID aliases.
return genomeMap, nil
}
// This map contains the locus values for the genome
// If a locus's entry doesn't exist, its value is unknown
// Map Structure: Locus rsID -> Locus Value
genomeLocusValuesMap := make(map[int64]locusValue.LocusValue)
for _, locusRSID := range traitLociList{
locusBasePairKnown, _, _, _, locusValueObject, err := GetLocusValueFromGenomeMap(checkForAliases, genomeMap, locusRSID)
if (err != nil) { return nil, err }
if (locusBasePairKnown == false){
continue
}
genomeLocusValuesMap[locusRSID] = locusValueObject
}
return genomeLocusValuesMap, nil
}
genomeLocusValuesMap, err := getGenomeLocusValuesMap()
if (err != nil) { return false, false, 0, nil, 0, 0, err }
traitName := traitObject.TraitName
neuralNetworkModelExists, traitPredictionIsPossible, predictedOutcome, predictionAccuracyRangesMap, quantityOfLociKnown, quantityOfPhasedLoci, err := geneticPrediction.GetNeuralNetworkNumericAttributePredictionFromGenomeMap(traitName, traitLociList, genomeLocusValuesMap)
if (err != nil) { return false, false, 0, nil, 0, 0, err }
if (neuralNetworkModelExists == false){
return false, false, 0, nil, 0, 0, nil
}
if (traitPredictionIsPossible == false){
return true, false, 0, nil, 0, 0, nil
}
return true, true, predictedOutcome, predictionAccuracyRangesMap, quantityOfLociKnown, quantityOfPhasedLoci, nil
}
// This function will retrieve the base pair of the locus from the input genome map
// We use this function 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)
// -bool: Locus base pair is phased
// -locusValue.LocusValue
// -error
func GetLocusValueFromGenomeMap(checkForAliases bool, inputGenomeMap map[int64]locusValue.LocusValue, locusRSID int64)(bool, string, string, bool, locusValue.LocusValue, 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
}
if (checkForAliases == false){
return false, locusValue.LocusValue{}, 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, locusValue.LocusValue{}, err }
if (locusValueFound == false){
return false, "", "", false, locusValue.LocusValue{}, nil
}
base1Value := locusValueObject.Base1Value
base2Value := locusValueObject.Base2Value
locusIsPhased := locusValueObject.LocusIsPhased
return true, base1Value, base2Value, locusIsPhased, locusValueObject, nil
}