2020
2121
2222class ObservableTransformation (str , Enum ):
23+ """Observable transformation types.
24+
25+ Observable transformations as used in the PEtab observables table.
26+ """
27+
2328 LIN = C .LIN
2429 LOG = C .LOG
2530 LOG10 = C .LOG10
2631
2732
2833class NoiseDistribution (str , Enum ):
34+ """Noise distribution types.
35+
36+ Noise distributions as used in the PEtab observables table.
37+ """
38+
2939 NORMAL = C .NORMAL
3040 LAPLACE = C .LAPLACE
3141
3242
3343class Observable (BaseModel ):
44+ """Observable definition."""
45+
3446 id : str = Field (alias = C .OBSERVABLE_ID )
3547 name : str | None = Field (alias = C .OBSERVABLE_NAME , default = None )
3648 formula : sp .Basic | None = Field (alias = C .OBSERVABLE_FORMULA , default = None )
@@ -82,6 +94,8 @@ class Config:
8294
8395
8496class ObservablesTable (BaseModel ):
97+ """PEtab observables table."""
98+
8599 observables : list [Observable ]
86100
87101 @classmethod
@@ -107,3 +121,169 @@ def from_tsv(cls, file_path: str | Path) -> ObservablesTable:
107121 def to_tsv (self , file_path : str | Path ) -> None :
108122 df = self .to_dataframe ()
109123 df .to_csv (file_path , sep = "\t " , index = False )
124+
125+
126+ class OperationType (str , Enum ):
127+ """Operation types for model changes in the PEtab conditions table."""
128+
129+ # TODO update names
130+ SET_CURRENT_VALUE = "setCurrentValue"
131+ SET_RATE = "setRate"
132+ SET_ASSIGNMENT = "setAssignment"
133+ CONSTANT = "constant"
134+ INITIAL = "initial"
135+ ...
136+
137+
138+ class Change (BaseModel ):
139+ """A change to the model or model state.
140+
141+ A change to the model or model state, corresponding to an individual
142+ row of the PEtab conditions table.
143+ """
144+
145+ target_id : str = Field (alias = C .TARGET_ID )
146+ operation_type : OperationType = Field (alias = C .VALUE_TYPE )
147+ target_value : sp .Basic = Field (alias = C .TARGET_VALUE )
148+
149+ class Config :
150+ populate_by_name = True
151+ arbitrary_types_allowed = True
152+ use_enum_values = True
153+
154+ @field_validator ("target_id" )
155+ @classmethod
156+ def validate_id (cls , v ):
157+ if not v :
158+ raise ValueError ("ID must not be empty." )
159+ if not is_valid_identifier (v ):
160+ raise ValueError (f"Invalid ID: { v } " )
161+ return v
162+
163+ @field_validator ("target_value" , mode = "before" )
164+ @classmethod
165+ def sympify (cls , v ):
166+ if v is None or isinstance (v , sp .Basic ):
167+ return v
168+ if isinstance (v , float ) and np .isnan (v ):
169+ return None
170+
171+ return sympify_petab (v )
172+
173+
174+ class ChangeSet (BaseModel ):
175+ """A set of changes to the model or model state.
176+
177+ A set of simultaneously occuring changes to the model or model state,
178+ corresponding to a perturbation of the underlying system. This corresponds
179+ to all rows of the PEtab conditions table with the same condition ID.
180+ """
181+
182+ id : str = Field (alias = C .CONDITION_ID )
183+ changes : list [Change ]
184+
185+ class Config :
186+ populate_by_name = True
187+
188+ @field_validator ("id" )
189+ @classmethod
190+ def validate_id (cls , v ):
191+ if not v :
192+ raise ValueError ("ID must not be empty." )
193+ if not is_valid_identifier (v ):
194+ raise ValueError (f"Invalid ID: { v } " )
195+ return v
196+
197+
198+ class ConditionsTable (BaseModel ):
199+ """PEtab conditions table."""
200+
201+ conditions : list [ChangeSet ]
202+
203+ @classmethod
204+ def from_dataframe (cls , df : pd .DataFrame ) -> ConditionsTable :
205+ if df is None :
206+ return cls (conditions = [])
207+
208+ conditions = []
209+ for condition_id , sub_df in df .groupby (C .CONDITION_ID ):
210+ changes = [Change (** row .to_dict ()) for _ , row in sub_df .iterrows ()]
211+ conditions .append (ChangeSet (id = condition_id , changes = changes ))
212+
213+ return cls (conditions = conditions )
214+
215+ def to_dataframe (self ) -> pd .DataFrame :
216+ records = [
217+ {C .CONDITION_ID : condition .id , ** change .model_dump ()}
218+ for condition in self .conditions
219+ for change in condition .changes
220+ ]
221+ return pd .DataFrame (records )
222+
223+ @classmethod
224+ def from_tsv (cls , file_path : str | Path ) -> ConditionsTable :
225+ df = pd .read_csv (file_path , sep = "\t " )
226+ return cls .from_dataframe (df )
227+
228+ def to_tsv (self , file_path : str | Path ) -> None :
229+ df = self .to_dataframe ()
230+ df .to_csv (file_path , sep = "\t " , index = False )
231+
232+
233+ class ExperimentPeriod (BaseModel ):
234+ """A period of a timecourse defined by a start time and a set changes.
235+
236+ This corresponds to a row of the PEtab experiments table.
237+ """
238+
239+ start : float = Field (alias = C .TIME )
240+ conditions : list [ChangeSet ]
241+
242+ class Config :
243+ populate_by_name = True
244+
245+
246+ class Experiment (BaseModel ):
247+ """An experiment or a timecourse defined by an ID and a set of different
248+ periods.
249+
250+ Corresponds to a group of rows of the PEtab experiments table with the same
251+ experiment ID.
252+ """
253+
254+ id : str = Field (alias = C .EXPERIMENT_ID )
255+ periods : list [ExperimentPeriod ]
256+
257+ class Config :
258+ populate_by_name = True
259+ arbitrary_types_allowed = True
260+
261+
262+ class ExperimentsTable (BaseModel ):
263+ """PEtab experiments table."""
264+
265+ experiments : list [Experiment ]
266+
267+ @classmethod
268+ def from_dataframe (cls , df : pd .DataFrame ) -> ExperimentsTable :
269+ if df is None :
270+ return cls (experiments = [])
271+
272+ experiments = [
273+ Experiment (** row .to_dict ())
274+ for _ , row in df .reset_index ().iterrows ()
275+ ]
276+
277+ return cls (experiments = experiments )
278+
279+ def to_dataframe (self ) -> pd .DataFrame :
280+ return pd .DataFrame (self .model_dump ()["experiments" ])
281+
282+ @classmethod
283+ def from_tsv (cls , file_path : str | Path ) -> ExperimentsTable :
284+ df = pd .read_csv (file_path , sep = "\t " )
285+ return cls .from_dataframe (df )
286+
287+ def to_tsv (self , file_path : str | Path ) -> None :
288+ df = self .to_dataframe ()
289+ df .to_csv (file_path , sep = "\t " , index = False )
0 commit comments