Skip to content

Commit

Permalink
Refactor to use jotai
Browse files Browse the repository at this point in the history
- Refactor front-end
- Minor adjustments back-end for statistics end-point (should use real filter)
  • Loading branch information
jorgenherje committed Dec 18, 2024
1 parent e823898 commit 6b9b179
Show file tree
Hide file tree
Showing 22 changed files with 702 additions and 358 deletions.
14 changes: 12 additions & 2 deletions backend_py/primary/primary/routers/timeseries/router.py
Original file line number Diff line number Diff line change
Expand Up @@ -199,11 +199,16 @@ async def get_statistical_vector_data_per_sensitivity(
vector_name: Annotated[str, Query(description="Name of the vector")],
resampling_frequency: Annotated[schemas.Frequency, Query(description="Resampling frequency")],
statistic_functions: Annotated[list[schemas.StatisticFunction] | None, Query(description="Optional list of statistics to calculate. If not specified, all statistics will be calculated.")] = None,
realizations_encoded_as_uint_list_str: Annotated[str | None, Query(description="Optional list of realizations to include. If not specified, all realizations will be included.")] = None,
# relative_to_timestamp: Annotated[datetime.datetime | None, Query(description="Calculate relative to timestamp")] = None,
# fmt:on
) -> list[schemas.VectorStatisticSensitivityData]:
"""Get statistical vector data for an ensemble per sensitivity"""

realizations: list[int] | None = None
if realizations_encoded_as_uint_list_str:
realizations = decode_uint_list_str(realizations_encoded_as_uint_list_str)

summmary_access = SummaryAccess.from_case_uuid(authenticated_user.get_sumo_access_token(), case_uuid, ensemble_name)
parameter_access = await ParameterAccess.from_case_uuid_async(
authenticated_user.get_sumo_access_token(), case_uuid, ensemble_name
Expand All @@ -218,10 +223,15 @@ async def get_statistical_vector_data_per_sensitivity(
ret_data: list[schemas.VectorStatisticSensitivityData] = []
if not sensitivities:
return ret_data


requested_realizations_mask = pc.is_in(vector_table["REAL"], value_set=pa.array(realizations)) if realizations else None
for sensitivity in sensitivities:
for case in sensitivity.cases:
mask = pc.is_in(vector_table["REAL"], value_set=pa.array(case.realizations))
table = vector_table.filter(mask)
sens_case_realization_mask = pc.is_in(vector_table["REAL"], value_set=pa.array(case.realizations))
if requested_realizations_mask is not None:
sens_case_realization_mask = pc.and_(requested_realizations_mask, sens_case_realization_mask)
table = vector_table.filter(sens_case_realization_mask)

statistics = compute_vector_statistics(table, vector_name, service_stat_funcs_to_compute)
if not statistics:
Expand Down
3 changes: 3 additions & 0 deletions frontend/src/api/services/TimeseriesService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -176,6 +176,7 @@ export class TimeseriesService {
* @param vectorName Name of the vector
* @param resamplingFrequency Resampling frequency
* @param statisticFunctions Optional list of statistics to calculate. If not specified, all statistics will be calculated.
* @param realizationsEncodedAsUintListStr Optional list of realizations to include. If not specified, all realizations will be included.
* @returns VectorStatisticSensitivityData Successful Response
* @throws ApiError
*/
Expand All @@ -185,6 +186,7 @@ export class TimeseriesService {
vectorName: string,
resamplingFrequency: Frequency,
statisticFunctions?: (Array<StatisticFunction> | null),
realizationsEncodedAsUintListStr?: (string | null),
): CancelablePromise<Array<VectorStatisticSensitivityData>> {
return this.httpRequest.request({
method: 'GET',
Expand All @@ -195,6 +197,7 @@ export class TimeseriesService {
'vector_name': vectorName,
'resampling_frequency': resamplingFrequency,
'statistic_functions': statisticFunctions,
'realizations_encoded_as_uint_list_str': realizationsEncodedAsUintListStr,
},
errors: {
422: `Validation Error`,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import { VectorRealizationData_api } from "@api";
import { DataGenerator } from "@framework/DataChannelTypes";
import { Ensemble } from "@framework/Ensemble";

import { indexOf } from "lodash";

export function makeVectorDataGenerator(
ensemble: Ensemble | null,
vectorRealizationData: VectorRealizationData_api[] | null,
activeTimestampUtcMs: number | null
): DataGenerator {
return () => {
const data: { key: number; value: number }[] = [];

if (ensemble && vectorRealizationData) {
vectorRealizationData.forEach((vec) => {
const indexOfTimestamp = indexOf(vec.timestamps_utc_ms, activeTimestampUtcMs);
data.push({
key: vec.realization,
value: indexOfTimestamp === -1 ? 0 : vec.values[indexOfTimestamp],
});
});
}
return {
data,
metaData: {
ensembleIdentString: ensemble?.getIdent().toString() ?? "",
unit: "unit",
},
};
};
}
22 changes: 8 additions & 14 deletions frontend/src/modules/SimulationTimeSeriesSensitivity/interfaces.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,23 +2,20 @@ import { Frequency_api } from "@api";
import { InterfaceInitialization } from "@framework/UniDirectionalModuleComponentsInterface";

import {
realizationsToIncludeAtom,
resamplingFrequencyAtom,
selectedSensitivitiesAtom,
showHistoricalAtom,
showRealizationsAtom,
showStatisticsAtom,
vectorSpecAtom,
} from "./settings/atoms/baseAtoms";
import { selectedSensitivityNamesAtom, vectorSpecificationAtom } from "./settings/atoms/derivedAtoms";
import { VectorSpec } from "./typesAndEnums";

type SettingsToViewInterface = {
vectorSpec: VectorSpec | null;
export type SettingsToViewInterface = {
vectorSpecification: VectorSpec | null;
resamplingFrequency: Frequency_api | null;
selectedSensitivities: string[] | null;
selectedSensitivityNames: string[] | null;
showStatistics: boolean;
showRealizations: boolean;
realizationsToInclude: number[] | null;
showHistorical: boolean;
};

Expand All @@ -27,24 +24,21 @@ export type Interfaces = {
};

export const settingsToViewInterfaceInitialization: InterfaceInitialization<SettingsToViewInterface> = {
vectorSpec: (get) => {
return get(vectorSpecAtom);
vectorSpecification: (get) => {
return get(vectorSpecificationAtom);
},
resamplingFrequency: (get) => {
return get(resamplingFrequencyAtom);
},
selectedSensitivities: (get) => {
return get(selectedSensitivitiesAtom);
selectedSensitivityNames: (get) => {
return get(selectedSensitivityNamesAtom);
},
showStatistics: (get) => {
return get(showStatisticsAtom);
},
showRealizations: (get) => {
return get(showRealizationsAtom);
},
realizationsToInclude: (get) => {
return get(realizationsToIncludeAtom);
},
showHistorical: (get) => {
return get(showHistoricalAtom);
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { ModuleRegistry } from "@framework/ModuleRegistry";

import { Interfaces, settingsToViewInterfaceInitialization } from "./interfaces";
import { Settings } from "./settings/settings";
import { View } from "./view";
import { View } from "./view/view";

const module = ModuleRegistry.initModule<Interfaces>("SimulationTimeSeriesSensitivity", {
settingsToViewInterfaceInitialization,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,19 @@
import { Frequency_api } from "@api";
import { VectorSpec } from "@modules/SimulationTimeSeriesSensitivity/typesAndEnums";
import { EnsembleIdent } from "@framework/EnsembleIdent";
import { atomWithCompare } from "@framework/utils/atomUtils";

import { atom } from "jotai";
import { isEqual } from "lodash";

export const syncedEnsembleIdentsAtom = atom<EnsembleIdent[] | null>(null);
export const syncedVectorNameAtom = atom<string | null>(null);

export const vectorSpecAtom = atom<VectorSpec | null>(null);
export const resamplingFrequencyAtom = atom<Frequency_api | null>(Frequency_api.MONTHLY);
export const selectedSensitivitiesAtom = atom<string[] | null>(null);
export const showStatisticsAtom = atom<boolean>(true);
export const showRealizationsAtom = atom<boolean>(false);
export const realizationsToIncludeAtom = atom<number[] | null>(null);
export const showHistoricalAtom = atom<boolean>(true);

export const userSelectedEnsembleIdentAtom = atom<EnsembleIdent | null>(null);
export const userSelectedVectorNameAtom = atom<string | null>(null);
export const userSelectedVectorTagAtom = atom<string | null>(null);
export const userSelectedSensitivityNamesAtom = atomWithCompare<string[] | null>(null, isEqual);
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
import { EnsembleIdent } from "@framework/EnsembleIdent";
import { EnsembleSetAtom } from "@framework/GlobalAtoms";
import { fixupEnsembleIdent, maybeAssignFirstSyncedEnsemble } from "@framework/utils/ensembleUiHelpers";
import { VectorSpec } from "@modules/SimulationTimeSeriesSensitivity/typesAndEnums";
import { createVectorSelectorDataFromVectors } from "@modules/_shared/components/VectorSelector";

import { atom } from "jotai";

import {
syncedEnsembleIdentsAtom,
syncedVectorNameAtom,
userSelectedEnsembleIdentAtom,
userSelectedSensitivityNamesAtom,
userSelectedVectorNameAtom,
userSelectedVectorTagAtom,
} from "./baseAtoms";
import { vectorListQueryAtom } from "./queryAtoms";

import { fixupVectorName } from "../utils/fixupVectorName";

export const selectedEnsembleIdentAtom = atom<EnsembleIdent | null>((get) => {
const syncedEnsembleIdents = get(syncedEnsembleIdentsAtom);
const userSelectedEnsembleIdent = get(userSelectedEnsembleIdentAtom);
const ensembleSet = get(EnsembleSetAtom);

const candidateEnsembleIdent = maybeAssignFirstSyncedEnsemble(userSelectedEnsembleIdent, syncedEnsembleIdents);
const fixedUpEnsembleIdent = fixupEnsembleIdent(candidateEnsembleIdent, ensembleSet);
return fixedUpEnsembleIdent;
});

export const availableSensitivityNamesAtom = atom<string[]>((get) => {
const ensembleSet = get(EnsembleSetAtom);
const selectedEnsembleIdent = get(selectedEnsembleIdentAtom);

const ensemble = selectedEnsembleIdent ? ensembleSet.findEnsemble(selectedEnsembleIdent) : null;
const ensembleSensitivityNames = ensemble?.getSensitivities()?.getSensitivityNames() ?? [];

return ensembleSensitivityNames;
});

export const selectedSensitivityNamesAtom = atom<string[]>((get) => {
const userSelectedSensitivityNames = get(userSelectedSensitivityNamesAtom);
const availableSensitivityNames = get(availableSensitivityNamesAtom);

// If userSelectedSensitivityNames is empty, do not override it
if (!userSelectedSensitivityNames || userSelectedSensitivityNames.length === 0) {
return [];
}

// Fixup invalid sensitivity names
// - If no valid sensitivity names are selected, the change can be due to new available sensitivity names
const fixedUpSensitivityNames =
userSelectedSensitivityNames?.filter((sens) => availableSensitivityNames.includes(sens)) ?? [];
if (fixedUpSensitivityNames.length === 0) {
return availableSensitivityNames;
}

return fixedUpSensitivityNames;
});

export const availableVectorNamesAtom = atom<string[]>((get) => {
const vectorListQuery = get(vectorListQueryAtom);
return vectorListQuery.data?.map((vec) => vec.name) ?? [];
});

export const vectorSelectorDataAtom = atom((get) => {
const isFetching = get(vectorListQueryAtom).isFetching;
const availableVectorNames = get(availableVectorNamesAtom);

if (isFetching) {
return [];
}

return createVectorSelectorDataFromVectors(availableVectorNames);
});

/**
* Atom that handles vector name and tag in synch with fixup
*/
const fixedUpVectorNameAndTagAtom = atom<{ name: string | null; tag: string | null }>((get) => {
const syncedVectorName = get(syncedVectorNameAtom);
const userSelectedVectorName = get(userSelectedVectorNameAtom);
const userSelectedVectorTag = get(userSelectedVectorTagAtom);
const availableVectorNames = get(availableVectorNamesAtom);

// Override with synced vector name if available
if (syncedVectorName) {
return { name: syncedVectorName, tag: syncedVectorName };
}

// If vector name is fixed up, adjust tag as well
const fixedUpVectorName = fixupVectorName(userSelectedVectorName, availableVectorNames);
if (fixedUpVectorName !== userSelectedVectorName) {
return { name: fixedUpVectorName, tag: fixedUpVectorName };
}

return { name: userSelectedVectorName, tag: userSelectedVectorTag };
});

export const selectedVectorNameAtom = atom<string | null>((get) => {
const fixedUpVectorName = get(fixedUpVectorNameAndTagAtom).name;
return fixedUpVectorName;
});

export const selectedVectorTagAtom = atom<string | null>((get) => {
const fixedUpVectorTag = get(fixedUpVectorNameAndTagAtom).tag;
return fixedUpVectorTag;
});

export const selectedVectorNameHasHistoricalAtom = atom<boolean>((get) => {
const selectedVectorName = get(selectedVectorNameAtom);
const vectorListQuery = get(vectorListQueryAtom);

const selectedVector = vectorListQuery.data?.find((vec) => vec.name === selectedVectorName);
return !!selectedVector?.has_historical;
});

export const vectorSpecificationAtom = atom<VectorSpec | null>((get) => {
const selectedEnsembleIdent = get(selectedEnsembleIdentAtom);
const selectedVectorName = get(selectedVectorNameAtom);
const selectedVectorNameHasHistorical = get(selectedVectorNameHasHistoricalAtom);

if (!selectedEnsembleIdent || !selectedVectorName) {
return null;
}

return {
ensembleIdent: selectedEnsembleIdent,
vectorName: selectedVectorName,
hasHistorical: selectedVectorNameHasHistorical,
};
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import { apiService } from "@framework/ApiService";

import { atomWithQuery } from "jotai-tanstack-query";

import { selectedEnsembleIdentAtom } from "./derivedAtoms";

const STALE_TIME = 60 * 1000;
const CACHE_TIME = 60 * 1000;

export const vectorListQueryAtom = atomWithQuery((get) => {
const selectedEnsembleIdent = get(selectedEnsembleIdentAtom);

const query = {
queryKey: ["getVectorList", selectedEnsembleIdent?.getCaseUuid(), selectedEnsembleIdent?.getEnsembleName()],
queryFn: () =>
apiService.timeseries.getVectorList(
selectedEnsembleIdent?.getCaseUuid() ?? "",
selectedEnsembleIdent?.getEnsembleName() ?? ""
),
staleTime: STALE_TIME,
gcTime: CACHE_TIME,
enabled: !!(selectedEnsembleIdent?.getCaseUuid() && selectedEnsembleIdent?.getEnsembleName()),
};

return query;
});

This file was deleted.

Loading

0 comments on commit 6b9b179

Please sign in to comment.