Results: Add TableData pamhyr object for np table.

opt-result
Pierre-Antoine 2026-07-03 14:43:50 +02:00
parent 1adf24ce77
commit e8f1f0ac43
4 changed files with 250 additions and 13 deletions

View File

@ -157,8 +157,185 @@ class AdditionalData(SQLSubModel):
return True
class TableData(SQLSubModel):
_sub_classes = []
def __init__(self, id=-1, study=None,
legend="", data=None,
owner_scenario=-1):
super(TableData, self).__init__(
id=id, status=study.status,
owner_scenario=owner_scenario
)
self._study = study
self._legend = legend
self._data = data
@property
def data(self):
return self._data
@classmethod
def _db_create(cls, execute, ext=""):
execute(f"""
CREATE TABLE results_table_data{ext} (
{cls.create_db_add_pamhyr_id()},
result INTEGER NOT NULL,
legend TEXT NOT NULL,
unit TEXT NOT NULL,
data_shape_x INTEGER NOT NULL,
data_shape_y INTEGER NOT NULL,
data_shape_z INTEGER DEFAULT -1,
data BLOB NOT NULL,
{Scenario.create_db_add_scenario()},
{Scenario.create_db_add_scenario_fk()},
FOREIGN KEY(result) REFERENCES results(pamhyr_id),
PRIMARY KEY(pamhyr_id, result, legend, scenario)
)
""")
if ext != "":
return True
return cls._create_submodel(execute)
@classmethod
def _db_update(cls, execute, version, data=None):
major, minor, release = version.strip().split(".")
create = False
if major == "0" and int(minor) == 2:
if int(release) < 7 and not create:
cls._db_create(execute)
create = True
return cls._update_submodel(execute, version, data)
@classmethod
def _db_load(cls, execute, data=None):
res = {}
study = data['study']
status = data['status']
scenario = data["scenario"]
result = data["result"]
rid = data["result_pid"]
table = execute(
"SELECT pamhyr_id, " +
"legend, unit, " +
"data_shape_x, data_shape_y, data_shape_z, " +
"data, " +
"scenario " +
"FROM results_table_data " +
f"WHERE scenario = {scenario.id} " +
f"AND result = {rid}"
)
for v in table:
it = iter(v)
pid = next(it)
legend = next(it)
unit = next(it)
data_shape_x = next(it)
data_shape_y = next(it)
data_shape_z = next(it)
data = next(it)
owner_scenario = next(it)
dtype = cls._unit_to_dtype(unit)
np_data = np.frombuffer(data, dtype=dtype)
if data_shape_y == -1:
np_data = np.reshape(np_data, data_shape_x)
elif data_shape_z == -1:
np_data = np.reshape(np_data, (data_shape_x,
data_shape_y))
else:
np_data = np.reshape(np_data, (data_shape_x,
data_shape_y,
data_shape_z))
new_table = cls(study=study,
legend=legend, data=np_data,
owner_scenario=owner_scenario)
res[legend] = new_table
return res
def _db_save(self, execute, data=None):
if self._status.scenario.id != self._owner_scenario:
return
shape_x = -1
shape_y = -1
shape_z = -1
shape = self._data.shape
if len(shape) == 2:
shape_x, shape_y = shape
elif len(shape == 1):
shape_x, = shape
elif len(shape == 3):
shape_x, shape_y, shape_z = shape
else:
logger.error(
f"Failed to parse numpy shape: {shape}"
)
execute(
"INSERT INTO " +
"results_table_data (pamhyr_id, result, " +
"legend, unit, " +
"data_shape_x, data_shape_y, data_shape_z, " +
"data, " +
"scenario) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)",
self._pamhyr_id, data["result"],
self._legend, self._dtype_to_unit(self._data.dtype),
shape_x, shape_y, shape_z,
self._data,
self._owner_scenario
)
return True
@classmethod
def _unit_to_dtype(cls, unit):
t = {
"i32": np.int32, "i64": np.int64,
"u32": np.uint32, "u64": np.uint64,
"f32": np.float32, "f64": np.float64,
}
return t[unit]
def _dtype_to_unit(self, dtype):
t = {
np.dtype(np.int32): "i32", np.dtype(np.int64): "i64",
np.dtype(np.uint32): "u32", np.dtype(np.uint64): "u64",
np.dtype(np.float32): "f32", np.dtype(np.float64): "f64",
}
return t[dtype]
def table(self):
return self._data
def __len__(self):
return len(self._data)
def __getitem__(self, key):
if isinstance(key, tuple):
return self._data[key[0], key[1]]
self._date[key]
class Results(SQLSubModel):
_sub_classes = [River, AdditionalData]
_sub_classes = [River, TableData, AdditionalData]
def __init__(self, id=-1, study=None, solver=None,
repertory="", name="0"):
@ -179,8 +356,11 @@ class Results(SQLSubModel):
"creation_date": datetime.now(),
"study_revision": study.status.version,
"additional_data": [],
"table": {},
}
self._ts_index = {}
if solver is not None:
self.set("solver_type", solver._type)
@ -221,6 +401,10 @@ class Results(SQLSubModel):
def get_timestamp_id(self, ts):
return self._ts_index[ts]
def compute_timestamp_index(self):
for i, ts in enumerate(self._meta_data["timestamps"]):
self._ts_index[ts] = i
def reload(self):
return self._solver.results(
self._study,
@ -318,11 +502,19 @@ class Results(SQLSubModel):
new_results.set("study_revision", revision)
new_results.set("creation_date", creation_date)
data["result_pid"] = pid
data["result"] = new_results
table_data = TableData._db_load(execute, data)
new_results.set("table", table_data)
sf = ">" + ''.join(itertools.repeat("d", nb_timestamps))
ts = struct.unpack(sf, timestamps_bytes)
new_results.set("timestamps", ts)
data["timestamps"] = sorted(ts)
new_results.compute_timestamp_index()
data["parent"] = new_results
new_results._river = River._db_load(execute, data)
@ -331,6 +523,20 @@ class Results(SQLSubModel):
AdditionalData._db_load(execute, data)
)
# Update global index
i = 0
for reach in new_results._river.reachs:
nb = len(reach.profiles)
reach.set_global_index(range(i, i + nb + 1))
i += nb
if "Z" in table_data:
for ts in data["timestamps"]:
new_results._river.update_water_limits(
ts,
table_data["Z"][new_results.get_timestamp_id(ts), :]
)
yield (solver_type, new_results)
def _db_save_clear(self, execute, solver_type, data=None):
@ -353,6 +559,11 @@ class Results(SQLSubModel):
f"WHERE scenario = {self._owner_scenario} " +
f"AND result = {pid}"
)
execute(
"DELETE FROM results_table_data " +
f"WHERE scenario = {self._owner_scenario} " +
f"AND result = {pid}"
)
execute(
"DELETE FROM results_add_data " +
f"WHERE scenario = {self._owner_scenario} " +
@ -396,7 +607,19 @@ class Results(SQLSubModel):
data["result"] = self._pamhyr_id
self._river._db_save(execute, data)
tables = self.get("table")
for legend in tables:
tables[legend]._db_save(execute, data)
for add_data in self.get("additional_data"):
add_data._db_save(execute, data)
return True
def new_table_data(self, legend, data):
return TableData(
study=self._study,
legend=legend,
data=data,
owner_scenario=self._owner_scenario
)

View File

@ -137,7 +137,6 @@ class Profile(SQLSubModel):
@classmethod
def _db_load(cls, execute, data=None):
new = {}
status = data['status']
parent = data['parent']
@ -148,6 +147,8 @@ class Profile(SQLSubModel):
loaded = data['loaded_pid']
timestamps = data['timestamps']
new = cls(profile, study, parent)
values = execute(
"SELECT pamhyr_id, result, key, " +
"len_data, data, scenario " +
@ -167,12 +168,6 @@ class Profile(SQLSubModel):
data = next(it)
owner_scenario = next(it)
if profile not in new:
new_data = cls(profile, study, parent)
new[profile] = new_data
else:
new_data = new[profile]
if key in ["Z", "Q", "V"]:
sf = ">" + ''.join(itertools.repeat("f", len_data))
len_values = len(values)
@ -185,11 +180,11 @@ class Profile(SQLSubModel):
)
for timestamp, value in zip(timestamps, values):
new_data.set(timestamp, key, value)
new.set(timestamp, key, value)
if key == "Z":
new_data.update_water_limits(timestamp, value)
new.update_water_limits(timestamp, value)
return list(new.values())
return new
@classmethod
def _db_load_data_sl_format(cls, values, len_data, timestamps):
@ -361,6 +356,10 @@ class Reach(SQLSubModel):
for i, p in enumerate(self._profiles):
self._buffers[key][:, i] = p.get_key(key)
def update_water_limits(self, timestamp, z_data):
for profile, z in zip(self._profiles, z_data):
profile.update_water_limits(timestamp, z)
@classmethod
def _db_create(cls, execute, ext=""):
return cls._create_submodel(execute)
@ -380,7 +379,7 @@ class Reach(SQLSubModel):
for i, profile in enumerate(reach.profiles):
data["profile"] = profile
new_reach._profiles += Profile._db_load(execute, data)
new_reach._profiles += [Profile._db_load(execute, data)]
return new_reach
@ -435,6 +434,15 @@ class River(SQLSubModel):
)
)
def update_water_limits(self, timestamp, data):
for reach in self._reachs:
ind = reach.global_index
reach.update_water_limits(
timestamp,
data[ind[0]:ind[1]]
)
@classmethod
def _db_create(cls, execute, ext=""):
return cls._create_submodel(execute)

View File

@ -46,7 +46,7 @@ logger = logging.getLogger()
class Study(SQLModel):
_version = "0.2.6"
_version = "0.2.7"
_sub_classes = [
Scenario,

View File

@ -1206,6 +1206,12 @@ class Mage8(Mage):
j += 1
table["V"] = velocity_arr
for legend in table:
table[legend] = results.new_table_data(
legend, table[legend]
)
results.set("table", table)
logger.info(f"read_bin: ... end with {len(ts)} timestamp read")