RSSeismicScripting 3.0.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- rsseismic/Application.py +196 -0
- rsseismic/ApplicationManager.py +111 -0
- rsseismic/BedrockLayer.py +29 -0
- rsseismic/MeanProfile.py +169 -0
- rsseismic/MeanProfileSoilLayer.py +119 -0
- rsseismic/Model.py +204 -0
- rsseismic/Motions.py +140 -0
- rsseismic/ProfileGenerationSettings.py +29 -0
- rsseismic/Profiles.py +186 -0
- rsseismic/ProjectSettings.py +46 -0
- rsseismic/ReferenceCurve.py +73 -0
- rsseismic/Results.py +419 -0
- rsseismic/SoilLayer.py +34 -0
- rsseismic/SoilLayerCurve.py +124 -0
- rsseismic/SoilLayerOperations.py +120 -0
- rsseismic/__init__.py +79 -0
- rsseismic/_application_paths.py +74 -0
- rsseismic/_batch_compute.py +420 -0
- rsseismic/_client.py +81 -0
- rsseismic/_proto_converters.py +345 -0
- rsseismic/_proxyObject.py +11 -0
- rsseismic/_result_bulk_dataframes.py +202 -0
- rsseismic/_result_dataframes.py +77 -0
- rsseismic/_rpc_helpers.py +55 -0
- rsseismic/enums/__init__.py +23 -0
- rsseismic/enums/_generated.py +120 -0
- rsseismic/enums/fitting_procedure.py +15 -0
- rsseismic/exceptions.py +11 -0
- rsseismic/generatedFiles/ApplicationService_pb2.py +46 -0
- rsseismic/generatedFiles/ApplicationService_pb2.pyi +67 -0
- rsseismic/generatedFiles/ApplicationService_pb2_grpc.py +274 -0
- rsseismic/generatedFiles/BedrockLayerDataService_pb2.py +27 -0
- rsseismic/generatedFiles/BedrockLayerDataService_pb2.pyi +5 -0
- rsseismic/generatedFiles/BedrockLayerDataService_pb2_grpc.py +231 -0
- rsseismic/generatedFiles/CommonMessages_pb2.py +50 -0
- rsseismic/generatedFiles/CommonMessages_pb2.pyi +113 -0
- rsseismic/generatedFiles/CommonMessages_pb2_grpc.py +29 -0
- rsseismic/generatedFiles/MeanProfileService_pb2.py +62 -0
- rsseismic/generatedFiles/MeanProfileService_pb2.pyi +176 -0
- rsseismic/generatedFiles/MeanProfileService_pb2_grpc.py +458 -0
- rsseismic/generatedFiles/MeanProfileSoilLayerDataService_pb2.py +45 -0
- rsseismic/generatedFiles/MeanProfileSoilLayerDataService_pb2.pyi +87 -0
- rsseismic/generatedFiles/MeanProfileSoilLayerDataService_pb2_grpc.py +360 -0
- rsseismic/generatedFiles/ModelService_pb2.py +54 -0
- rsseismic/generatedFiles/ModelService_pb2.pyi +122 -0
- rsseismic/generatedFiles/ModelService_pb2_grpc.py +360 -0
- rsseismic/generatedFiles/MotionService_pb2.py +78 -0
- rsseismic/generatedFiles/MotionService_pb2.pyi +224 -0
- rsseismic/generatedFiles/MotionService_pb2_grpc.py +575 -0
- rsseismic/generatedFiles/ProfileGenerationSettingsService_pb2.py +27 -0
- rsseismic/generatedFiles/ProfileGenerationSettingsService_pb2.pyi +5 -0
- rsseismic/generatedFiles/ProfileGenerationSettingsService_pb2_grpc.py +145 -0
- rsseismic/generatedFiles/ProfileService_pb2.py +76 -0
- rsseismic/generatedFiles/ProfileService_pb2.pyi +246 -0
- rsseismic/generatedFiles/ProfileService_pb2_grpc.py +532 -0
- rsseismic/generatedFiles/ProjectSettingsDataService_pb2.py +35 -0
- rsseismic/generatedFiles/ProjectSettingsDataService_pb2.pyi +40 -0
- rsseismic/generatedFiles/ProjectSettingsDataService_pb2_grpc.py +232 -0
- rsseismic/generatedFiles/ReferenceCurveDataService_pb2.py +41 -0
- rsseismic/generatedFiles/ReferenceCurveDataService_pb2.pyi +73 -0
- rsseismic/generatedFiles/ReferenceCurveDataService_pb2_grpc.py +281 -0
- rsseismic/generatedFiles/ResultsService_pb2.py +82 -0
- rsseismic/generatedFiles/ResultsService_pb2.pyi +373 -0
- rsseismic/generatedFiles/ResultsService_pb2_grpc.py +489 -0
- rsseismic/generatedFiles/SoilLayerCurveDataService_pb2.py +52 -0
- rsseismic/generatedFiles/SoilLayerCurveDataService_pb2.pyi +150 -0
- rsseismic/generatedFiles/SoilLayerCurveDataService_pb2_grpc.py +280 -0
- rsseismic/generatedFiles/SoilLayerDataService_pb2.py +27 -0
- rsseismic/generatedFiles/SoilLayerDataService_pb2.pyi +5 -0
- rsseismic/generatedFiles/SoilLayerDataService_pb2_grpc.py +231 -0
- rsseismic/generatedFiles/SoilLayerService_pb2.py +46 -0
- rsseismic/generatedFiles/SoilLayerService_pb2.pyi +122 -0
- rsseismic/generatedFiles/SoilLayerService_pb2_grpc.py +317 -0
- rsseismic/generatedFiles/__init__.py +4 -0
- rsseismic/properties/_PropertyAccessor.py +151 -0
- rsseismic/properties/__init__.py +6 -0
- rsseismic/properties/bedrockLayer/__init__.py +1 -0
- rsseismic/properties/bedrockLayer/_generated.py +127 -0
- rsseismic/properties/meanProfileSoilLayer/__init__.py +1 -0
- rsseismic/properties/meanProfileSoilLayer/_generated.py +87 -0
- rsseismic/properties/profileGenerationSettings/__init__.py +1 -0
- rsseismic/properties/profileGenerationSettings/_generated.py +195 -0
- rsseismic/properties/projectSettings/__init__.py +1 -0
- rsseismic/properties/projectSettings/_generated.py +270 -0
- rsseismic/properties/referenceCurve/__init__.py +1 -0
- rsseismic/properties/referenceCurve/_generated.py +264 -0
- rsseismic/properties/soilLayer/__init__.py +1 -0
- rsseismic/properties/soilLayer/_generated.py +523 -0
- rsseismic/types.py +311 -0
- rsseismicscripting-3.0.0.dist-info/METADATA +88 -0
- rsseismicscripting-3.0.0.dist-info/RECORD +94 -0
- rsseismicscripting-3.0.0.dist-info/WHEEL +5 -0
- rsseismicscripting-3.0.0.dist-info/licenses/License.txt +20 -0
- rsseismicscripting-3.0.0.dist-info/top_level.txt +1 -0
rsseismic/Results.py
ADDED
|
@@ -0,0 +1,419 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from collections.abc import Sequence
|
|
4
|
+
|
|
5
|
+
import pandas as pd
|
|
6
|
+
|
|
7
|
+
from rsseismic._client import Client
|
|
8
|
+
from rsseismic._proto_converters import from_result_database_entry
|
|
9
|
+
from rsseismic._proto_converters import (
|
|
10
|
+
from_result_database_entry,
|
|
11
|
+
from_result_layer_entry,
|
|
12
|
+
from_result_motion_entry,
|
|
13
|
+
)
|
|
14
|
+
from rsseismic._proxyObject import _ProxyObject
|
|
15
|
+
from rsseismic._result_bulk_dataframes import (
|
|
16
|
+
fetch_bulk_dataframes,
|
|
17
|
+
fetch_bulk_layer_series_dataframes,
|
|
18
|
+
fetch_bulk_profile_series_dataframes,
|
|
19
|
+
infer_el_convergence_motion_type_filter,
|
|
20
|
+
normalize_result_motion_type_filters,
|
|
21
|
+
resolve_layer_ids,
|
|
22
|
+
select_convergence_tab_motion_names,
|
|
23
|
+
select_displacement_animation_motion_names,
|
|
24
|
+
select_profile_motion_names,
|
|
25
|
+
)
|
|
26
|
+
from rsseismic._result_dataframes import data_table_to_dataframe, resolve_result_series_kind
|
|
27
|
+
from rsseismic._rpc_helpers import require_success
|
|
28
|
+
from rsseismic.generatedFiles import ResultsService_pb2 as ResultsService_pb2
|
|
29
|
+
from rsseismic.generatedFiles import ResultsService_pb2_grpc as ResultsService_pb2_grpc
|
|
30
|
+
from rsseismic.types import (
|
|
31
|
+
ResultDatabaseInfo,
|
|
32
|
+
ResultLayerSummary,
|
|
33
|
+
ResultMotionSummary,
|
|
34
|
+
ResultMotionType,
|
|
35
|
+
ResultSeriesKind,
|
|
36
|
+
)
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
class Results(_ProxyObject):
|
|
40
|
+
"""
|
|
41
|
+
Query computed analysis results for an open project.
|
|
42
|
+
|
|
43
|
+
Access via ``model.Results``. After ``model.runCompute()``, use ``listResultDatabases``
|
|
44
|
+
or ``listResultMotions()`` to discover valid profile and motion names, then fetch
|
|
45
|
+
tabular data as pandas ``DataFrame`` objects.
|
|
46
|
+
|
|
47
|
+
Use ``getResultDatabasePath`` to see which result files exist for a profile/motion
|
|
48
|
+
pair before calling specialized queries (strain convergence, displacement animation, etc.).
|
|
49
|
+
"""
|
|
50
|
+
|
|
51
|
+
def __init__(self, client: Client, modelId: str):
|
|
52
|
+
super().__init__(client, modelId)
|
|
53
|
+
self._results_service = ResultsService_pb2_grpc.ResultsServiceStub(self._client.channel)
|
|
54
|
+
|
|
55
|
+
def getResultsRoot(self) -> str:
|
|
56
|
+
"""Return the folder where RSSeismic stores computed results for this project."""
|
|
57
|
+
request = ResultsService_pb2.GetResultsRootRequest(modelID=self._objectId)
|
|
58
|
+
response = self._client.callFunction(self._results_service.GetResultsRoot, request)
|
|
59
|
+
require_success(response, "GetResultsRoot")
|
|
60
|
+
return response.resultsRoot
|
|
61
|
+
|
|
62
|
+
def listResultDatabases(
|
|
63
|
+
self,
|
|
64
|
+
profileName: str = "",
|
|
65
|
+
onlyExisting: bool = False,
|
|
66
|
+
) -> list[ResultDatabaseInfo]:
|
|
67
|
+
"""
|
|
68
|
+
List profile/motion pairs and their result file paths.
|
|
69
|
+
|
|
70
|
+
When ``profileName`` is empty, entries for all profiles are returned.
|
|
71
|
+
When ``onlyExisting`` is True, only pairs whose main result database file
|
|
72
|
+
exists on disk are included.
|
|
73
|
+
|
|
74
|
+
Use the ``motionName`` values from these entries (or from ``listResultMotions``)
|
|
75
|
+
when calling result DataFrame methods — they may differ from names shown in
|
|
76
|
+
the Motion view, especially for complementary analyses.
|
|
77
|
+
"""
|
|
78
|
+
request = ResultsService_pb2.ListResultDatabasesRequest(
|
|
79
|
+
modelID=self._objectId,
|
|
80
|
+
profileName=profileName,
|
|
81
|
+
onlyExisting=onlyExisting,
|
|
82
|
+
)
|
|
83
|
+
response = self._client.callFunction(self._results_service.ListResultDatabases, request)
|
|
84
|
+
require_success(response, "ListResultDatabases")
|
|
85
|
+
return [from_result_database_entry(entry) for entry in response.entries]
|
|
86
|
+
|
|
87
|
+
def getResultDatabasePath(
|
|
88
|
+
self,
|
|
89
|
+
profileName: str,
|
|
90
|
+
motionName: str,
|
|
91
|
+
) -> ResultDatabaseInfo:
|
|
92
|
+
"""
|
|
93
|
+
Return result file paths and availability flags for one profile and motion.
|
|
94
|
+
|
|
95
|
+
``motionName`` must be a compute-aligned name from ``listResultDatabases`` or
|
|
96
|
+
``listResultMotions`` after ``runCompute()``. Check fields such as
|
|
97
|
+
``resultDatabaseExists``, ``strainConvergenceExists``, and
|
|
98
|
+
``deconvolutionBedrockMotionExists`` before calling related DataFrame methods.
|
|
99
|
+
"""
|
|
100
|
+
request = ResultsService_pb2.GetResultDatabasePathRequest(
|
|
101
|
+
modelID=self._objectId,
|
|
102
|
+
profileName=profileName,
|
|
103
|
+
motionName=motionName,
|
|
104
|
+
)
|
|
105
|
+
response = self._client.callFunction(self._results_service.GetResultDatabasePath, request)
|
|
106
|
+
require_success(response, "GetResultDatabasePath")
|
|
107
|
+
return from_result_database_entry(response.entry)
|
|
108
|
+
|
|
109
|
+
def listResultMotions(self) -> list[ResultMotionSummary]:
|
|
110
|
+
"""
|
|
111
|
+
List motion names available for result queries after compute.
|
|
112
|
+
|
|
113
|
+
Includes complementary motions when project settings add them (for example
|
|
114
|
+
``ChiChi-EL`` derived from selected motion ``ChiChi``). Prefer these names over
|
|
115
|
+
Motion-view labels when fetching results.
|
|
116
|
+
"""
|
|
117
|
+
request = ResultsService_pb2.ListResultMotionsRequest(modelID=self._objectId)
|
|
118
|
+
response = self._client.callFunction(self._results_service.ListResultMotions, request)
|
|
119
|
+
require_success(response, "ListResultMotions")
|
|
120
|
+
return [from_result_motion_entry(motion) for motion in response.motions]
|
|
121
|
+
|
|
122
|
+
def listResultLayers(self, profileName: str) -> list[ResultLayerSummary]:
|
|
123
|
+
"""
|
|
124
|
+
List layers that can be used with layer-scoped result queries for a profile.
|
|
125
|
+
|
|
126
|
+
Includes soil layers with result output enabled and the top-of-rock layer when
|
|
127
|
+
bedrock output is configured.
|
|
128
|
+
"""
|
|
129
|
+
if not profileName:
|
|
130
|
+
raise ValueError("Profile name is required.")
|
|
131
|
+
request = ResultsService_pb2.ListResultLayersRequest(
|
|
132
|
+
modelID=self._objectId,
|
|
133
|
+
profileName=profileName,
|
|
134
|
+
)
|
|
135
|
+
response = self._client.callFunction(self._results_service.ListResultLayers, request)
|
|
136
|
+
require_success(response, "ListResultLayers")
|
|
137
|
+
return [from_result_layer_entry(layer) for layer in response.layers]
|
|
138
|
+
|
|
139
|
+
def getTopOfRockLayerId(self, profileName: str) -> str:
|
|
140
|
+
"""
|
|
141
|
+
Return the layer ID to use for top-of-rock result queries on a profile.
|
|
142
|
+
|
|
143
|
+
Raises ValueError when top of rock output is not available in
|
|
144
|
+
``listResultLayers(profileName)``.
|
|
145
|
+
"""
|
|
146
|
+
for layer in self.listResultLayers(profileName):
|
|
147
|
+
if layer.isTopOfRock:
|
|
148
|
+
return layer.layerID
|
|
149
|
+
raise ValueError(
|
|
150
|
+
f"Top of rock is not available for profile '{profileName}'. "
|
|
151
|
+
"Enable bedrockOutputDeconvolution and ensure the profile is configured for top of rock output."
|
|
152
|
+
)
|
|
153
|
+
|
|
154
|
+
def getLayerTimeHistoryDataFrame(
|
|
155
|
+
self,
|
|
156
|
+
profileName: str,
|
|
157
|
+
motionName: str,
|
|
158
|
+
layerID: str,
|
|
159
|
+
) -> pd.DataFrame:
|
|
160
|
+
"""
|
|
161
|
+
Return layer time-history results as a pandas DataFrame.
|
|
162
|
+
|
|
163
|
+
Columns typically include time, acceleration, velocity, and related series for
|
|
164
|
+
the given layer. For finite-element analysis, effective vertical stress may also
|
|
165
|
+
be included.
|
|
166
|
+
|
|
167
|
+
``motionName`` must be compute-aligned. ``layerID`` comes from
|
|
168
|
+
``listResultLayers(profileName)`` or ``listSoilLayers()`` on the same profile.
|
|
169
|
+
Column units are in ``dataframe.attrs["column_units"]`` when available.
|
|
170
|
+
"""
|
|
171
|
+
request = ResultsService_pb2.GetLayerTimeHistoryTableRequest(
|
|
172
|
+
modelID=self._objectId,
|
|
173
|
+
profileName=profileName,
|
|
174
|
+
motionName=motionName,
|
|
175
|
+
layerID=layerID,
|
|
176
|
+
)
|
|
177
|
+
response = self._client.callFunction(
|
|
178
|
+
self._results_service.GetLayerTimeHistoryTable,
|
|
179
|
+
request,
|
|
180
|
+
)
|
|
181
|
+
require_success(response, "GetLayerTimeHistoryTable")
|
|
182
|
+
return data_table_to_dataframe(response.table)
|
|
183
|
+
|
|
184
|
+
def getProfileResultsDataFrame(
|
|
185
|
+
self,
|
|
186
|
+
profileName: str,
|
|
187
|
+
motionName: str,
|
|
188
|
+
) -> pd.DataFrame:
|
|
189
|
+
"""
|
|
190
|
+
Return profile-level summary results as a pandas DataFrame.
|
|
191
|
+
|
|
192
|
+
Provides depth-based summary columns for the profile (for example maximum strain
|
|
193
|
+
by layer). Not available for deconvolution profiles — use layer-scoped methods instead.
|
|
194
|
+
|
|
195
|
+
``motionName`` must be compute-aligned after ``runCompute()``.
|
|
196
|
+
"""
|
|
197
|
+
request = ResultsService_pb2.GetProfileResultsTableRequest(
|
|
198
|
+
modelID=self._objectId,
|
|
199
|
+
profileName=profileName,
|
|
200
|
+
motionName=motionName,
|
|
201
|
+
)
|
|
202
|
+
response = self._client.callFunction(
|
|
203
|
+
self._results_service.GetProfileResultsTable,
|
|
204
|
+
request,
|
|
205
|
+
)
|
|
206
|
+
require_success(response, "GetProfileResultsTable")
|
|
207
|
+
return data_table_to_dataframe(response.table)
|
|
208
|
+
|
|
209
|
+
def getResultSeriesDataFrame(
|
|
210
|
+
self,
|
|
211
|
+
profileName: str,
|
|
212
|
+
motionName: str,
|
|
213
|
+
kind: ResultSeriesKind,
|
|
214
|
+
layerID: str = "",
|
|
215
|
+
) -> pd.DataFrame:
|
|
216
|
+
"""
|
|
217
|
+
Return one result chart series as a pandas DataFrame.
|
|
218
|
+
|
|
219
|
+
Pass a ``ResultSeriesKind`` value for ``kind`` (for example ``MAX_STRAIN``,
|
|
220
|
+
``PGA``, ``ACCELERATION``). When ``kind.requires_layer`` is True, ``layerID``
|
|
221
|
+
is required — use ``listResultLayers(profileName)`` to obtain valid IDs.
|
|
222
|
+
|
|
223
|
+
``motionName`` must be compute-aligned after ``runCompute()``.
|
|
224
|
+
"""
|
|
225
|
+
request = ResultsService_pb2.GetResultSeriesRequest(
|
|
226
|
+
modelID=self._objectId,
|
|
227
|
+
profileName=profileName,
|
|
228
|
+
motionName=motionName,
|
|
229
|
+
kind=resolve_result_series_kind(kind),
|
|
230
|
+
layerID=layerID,
|
|
231
|
+
)
|
|
232
|
+
response = self._client.callFunction(
|
|
233
|
+
self._results_service.GetResultSeries,
|
|
234
|
+
request,
|
|
235
|
+
)
|
|
236
|
+
require_success(response, "GetResultSeries")
|
|
237
|
+
return data_table_to_dataframe(response.table)
|
|
238
|
+
|
|
239
|
+
def getStrainConvergenceDataFrame(
|
|
240
|
+
self,
|
|
241
|
+
profileName: str,
|
|
242
|
+
motionName: str,
|
|
243
|
+
) -> pd.DataFrame:
|
|
244
|
+
"""
|
|
245
|
+
Return equivalent-linear strain convergence data as a pandas DataFrame.
|
|
246
|
+
|
|
247
|
+
Table columns show iteration and per-layer strain values. Requires equivalent-linear
|
|
248
|
+
analysis and a computed strain-convergence file for the motion. Check
|
|
249
|
+
``getResultDatabasePath(...).strainConvergenceExists`` before calling.
|
|
250
|
+
|
|
251
|
+
For complementary equivalent-linear runs, pass the complementary motion name
|
|
252
|
+
(for example ``ChiChi-EL``).
|
|
253
|
+
"""
|
|
254
|
+
request = ResultsService_pb2.GetStrainConvergenceTableRequest(
|
|
255
|
+
modelID=self._objectId,
|
|
256
|
+
profileName=profileName,
|
|
257
|
+
motionName=motionName,
|
|
258
|
+
)
|
|
259
|
+
response = self._client.callFunction(
|
|
260
|
+
self._results_service.GetStrainConvergenceTable,
|
|
261
|
+
request,
|
|
262
|
+
)
|
|
263
|
+
require_success(response, "GetStrainConvergenceTable")
|
|
264
|
+
return data_table_to_dataframe(response.table)
|
|
265
|
+
|
|
266
|
+
def getRealTimeDisplacementDataFrame(
|
|
267
|
+
self,
|
|
268
|
+
profileName: str,
|
|
269
|
+
motionName: str,
|
|
270
|
+
) -> pd.DataFrame:
|
|
271
|
+
"""
|
|
272
|
+
Return real-time relative displacement at each layer as a pandas DataFrame.
|
|
273
|
+
|
|
274
|
+
Requires the displacement animation option enabled in project settings and a
|
|
275
|
+
standard result database for the motion.
|
|
276
|
+
"""
|
|
277
|
+
request = ResultsService_pb2.GetRealTimeDisplacementTableRequest(
|
|
278
|
+
modelID=self._objectId,
|
|
279
|
+
profileName=profileName,
|
|
280
|
+
motionName=motionName,
|
|
281
|
+
)
|
|
282
|
+
response = self._client.callFunction(
|
|
283
|
+
self._results_service.GetRealTimeDisplacementTable,
|
|
284
|
+
request,
|
|
285
|
+
)
|
|
286
|
+
require_success(response, "GetRealTimeDisplacementTable")
|
|
287
|
+
return data_table_to_dataframe(response.table)
|
|
288
|
+
|
|
289
|
+
def getAllStrainConvergenceDataFrames(
|
|
290
|
+
self,
|
|
291
|
+
profileName: str,
|
|
292
|
+
*,
|
|
293
|
+
skipErrors: bool = True,
|
|
294
|
+
) -> dict[str, pd.DataFrame]:
|
|
295
|
+
"""
|
|
296
|
+
Fetch strain convergence DataFrames for all motions on the Check convergence tab.
|
|
297
|
+
|
|
298
|
+
Uses the same motion list as that tab in the RSSeismic UI: primary equivalent-linear
|
|
299
|
+
motions, plus complementary EL motions when that option is enabled. Each entry is
|
|
300
|
+
loaded via ``getStrainConvergenceDataFrame``.
|
|
301
|
+
|
|
302
|
+
When ``skipErrors`` is True (default), motions without convergence data are skipped
|
|
303
|
+
instead of raising an error.
|
|
304
|
+
"""
|
|
305
|
+
self._require_profile_name(profileName)
|
|
306
|
+
motion_names = select_convergence_tab_motion_names(
|
|
307
|
+
self.listResultMotions(),
|
|
308
|
+
self._resolve_el_convergence_motion_type_filter(),
|
|
309
|
+
)
|
|
310
|
+
return fetch_bulk_dataframes(
|
|
311
|
+
motion_names,
|
|
312
|
+
lambda motion_name: self.getStrainConvergenceDataFrame(profileName, motion_name),
|
|
313
|
+
skip_errors=skipErrors,
|
|
314
|
+
)
|
|
315
|
+
|
|
316
|
+
def getAllRealTimeDisplacementDataFrames(
|
|
317
|
+
self,
|
|
318
|
+
profileName: str,
|
|
319
|
+
*,
|
|
320
|
+
skipErrors: bool = True,
|
|
321
|
+
) -> dict[str, pd.DataFrame]:
|
|
322
|
+
"""
|
|
323
|
+
Fetch real-time displacement DataFrames for all result motions on a profile.
|
|
324
|
+
|
|
325
|
+
Uses the same motion list as the displacement animation tab. Requires the
|
|
326
|
+
displacement animation project setting. When ``skipErrors`` is True (default),
|
|
327
|
+
motions without data are skipped instead of raising an error.
|
|
328
|
+
"""
|
|
329
|
+
self._require_profile_name(profileName)
|
|
330
|
+
motion_names = select_displacement_animation_motion_names(self.listResultMotions())
|
|
331
|
+
return fetch_bulk_dataframes(
|
|
332
|
+
motion_names,
|
|
333
|
+
lambda motion_name: self.getRealTimeDisplacementDataFrame(profileName, motion_name),
|
|
334
|
+
skip_errors=skipErrors,
|
|
335
|
+
)
|
|
336
|
+
|
|
337
|
+
def getAllResultSeriesDataFrames(
|
|
338
|
+
self,
|
|
339
|
+
profileName: str,
|
|
340
|
+
kind: ResultSeriesKind,
|
|
341
|
+
motionTypeFilters: Sequence[ResultMotionType] | None = None,
|
|
342
|
+
layerIds: Sequence[str] | None = None,
|
|
343
|
+
*,
|
|
344
|
+
skipErrors: bool = True,
|
|
345
|
+
) -> dict[str, pd.DataFrame] | dict[tuple[str, str], pd.DataFrame]:
|
|
346
|
+
"""
|
|
347
|
+
Fetch one result series kind for many motions (and layers when required).
|
|
348
|
+
|
|
349
|
+
Returns a dict keyed by motion name for profile-level series, or by
|
|
350
|
+
``(motion_name, layer_id)`` when ``kind.requires_layer`` is True.
|
|
351
|
+
|
|
352
|
+
``motionTypeFilters`` limits which motion variants are included (standard,
|
|
353
|
+
complementary equivalent-linear, complementary nonlinear total stress). Pass
|
|
354
|
+
``None`` or an empty sequence to include all types.
|
|
355
|
+
|
|
356
|
+
``layerIds`` limits layer-scoped series. When omitted or empty and ``kind``
|
|
357
|
+
requires a layer, all layers from ``listResultLayers(profileName)`` are used.
|
|
358
|
+
|
|
359
|
+
When ``skipErrors`` is True (default), missing combinations are skipped
|
|
360
|
+
instead of raising an error.
|
|
361
|
+
"""
|
|
362
|
+
self._require_profile_name(profileName)
|
|
363
|
+
resolved_kind = ResultSeriesKind(resolve_result_series_kind(kind))
|
|
364
|
+
motion_type_filters = normalize_result_motion_type_filters(motionTypeFilters)
|
|
365
|
+
motion_names = select_profile_motion_names(
|
|
366
|
+
self._list_result_databases(profileName, onlyExisting=True),
|
|
367
|
+
profileName,
|
|
368
|
+
motion_type_filters,
|
|
369
|
+
require_result_database=True,
|
|
370
|
+
)
|
|
371
|
+
|
|
372
|
+
if resolved_kind.requires_layer:
|
|
373
|
+
layer_ids = resolve_layer_ids(
|
|
374
|
+
layerIds,
|
|
375
|
+
lambda: self.listResultLayers(profileName),
|
|
376
|
+
)
|
|
377
|
+
return fetch_bulk_layer_series_dataframes(
|
|
378
|
+
motion_names,
|
|
379
|
+
layer_ids,
|
|
380
|
+
lambda motion_name, layer_id: self.getResultSeriesDataFrame(
|
|
381
|
+
profileName,
|
|
382
|
+
motion_name,
|
|
383
|
+
resolved_kind,
|
|
384
|
+
layer_id,
|
|
385
|
+
),
|
|
386
|
+
skip_errors=skipErrors,
|
|
387
|
+
)
|
|
388
|
+
|
|
389
|
+
return fetch_bulk_profile_series_dataframes(
|
|
390
|
+
motion_names,
|
|
391
|
+
lambda motion_name: self.getResultSeriesDataFrame(
|
|
392
|
+
profileName,
|
|
393
|
+
motion_name,
|
|
394
|
+
resolved_kind,
|
|
395
|
+
),
|
|
396
|
+
skip_errors=skipErrors,
|
|
397
|
+
)
|
|
398
|
+
|
|
399
|
+
def _require_profile_name(self, profileName: str) -> None:
|
|
400
|
+
if not profileName:
|
|
401
|
+
raise ValueError("Profile name is required.")
|
|
402
|
+
|
|
403
|
+
def _list_result_databases(
|
|
404
|
+
self,
|
|
405
|
+
profileName: str,
|
|
406
|
+
*,
|
|
407
|
+
onlyExisting: bool,
|
|
408
|
+
) -> list[ResultDatabaseInfo]:
|
|
409
|
+
return self.listResultDatabases(profileName=profileName, onlyExisting=onlyExisting)
|
|
410
|
+
|
|
411
|
+
def _resolve_el_convergence_motion_type_filter(self) -> frozenset[ResultMotionType] | None:
|
|
412
|
+
from rsseismic.ProjectSettings import ProjectSettings
|
|
413
|
+
|
|
414
|
+
data = ProjectSettings(self._client, self._objectId).Data
|
|
415
|
+
return infer_el_convergence_motion_type_filter(
|
|
416
|
+
data.getEnumValue("solutionType", str),
|
|
417
|
+
data.getEnumValue("analysisMode", str),
|
|
418
|
+
data.getBoolValue("equivalentLinear"),
|
|
419
|
+
)
|
rsseismic/SoilLayer.py
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from rsseismic._client import Client
|
|
4
|
+
from rsseismic._proxyObject import _ProxyObject
|
|
5
|
+
from rsseismic.generatedFiles import SoilLayerDataService_pb2_grpc as SoilLayerDataService_pb2_grpc
|
|
6
|
+
from rsseismic.properties._PropertyAccessor import ProjectScopedPropertyAccessor
|
|
7
|
+
from rsseismic.properties.soilLayer._generated import SoilLayerPropertiesMixin
|
|
8
|
+
from rsseismic.ReferenceCurve import ReferenceCurve
|
|
9
|
+
from rsseismic.SoilLayerCurve import SoilLayerCurve
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class SoilLayer(_ProxyObject, SoilLayerPropertiesMixin):
|
|
13
|
+
"""
|
|
14
|
+
Read and edit properties for one soil layer on a profile.
|
|
15
|
+
|
|
16
|
+
Obtain an instance from ``model.Profiles.getSoilLayer(layerId)``. Layer IDs
|
|
17
|
+
come from ``model.Profiles.listSoilLayers()`` or ``listActiveSoilLayers()``.
|
|
18
|
+
|
|
19
|
+
Use the generated ``get*`` / ``set*`` helpers for scalar layer properties such as
|
|
20
|
+
thickness, unit weight, and soil model. Reference curves are edited through
|
|
21
|
+
``layer.ReferenceCurve``; fitted dynamics and curve fitting use ``layer.Curve``.
|
|
22
|
+
"""
|
|
23
|
+
|
|
24
|
+
def __init__(self, client: Client, modelId: str, layerId: str):
|
|
25
|
+
super().__init__(client, layerId)
|
|
26
|
+
self.modelId = modelId
|
|
27
|
+
stub = SoilLayerDataService_pb2_grpc.SoilLayerDataServiceStub(self._client.channel)
|
|
28
|
+
self.Data = ProjectScopedPropertyAccessor(client, modelId, layerId, stub)
|
|
29
|
+
self.ReferenceCurve = ReferenceCurve(client, modelId, layerId)
|
|
30
|
+
self.Curve = SoilLayerCurve(client, modelId, layerId)
|
|
31
|
+
|
|
32
|
+
@property
|
|
33
|
+
def layerId(self) -> str:
|
|
34
|
+
return self._objectId
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import Optional
|
|
4
|
+
|
|
5
|
+
from rsseismic._client import Client
|
|
6
|
+
from rsseismic._proto_converters import (
|
|
7
|
+
from_discrete_points,
|
|
8
|
+
from_dynamics_points,
|
|
9
|
+
to_discrete_points,
|
|
10
|
+
to_dynamics_points,
|
|
11
|
+
to_gq_h_fitting_limits_proto,
|
|
12
|
+
)
|
|
13
|
+
from rsseismic._proxyObject import _ProxyObject
|
|
14
|
+
from rsseismic._rpc_helpers import require_success
|
|
15
|
+
from rsseismic.enums import FittingProcedureModel
|
|
16
|
+
from rsseismic.generatedFiles import SoilLayerCurveDataService_pb2 as SoilLayerCurveDataService_pb2
|
|
17
|
+
from rsseismic.generatedFiles import SoilLayerCurveDataService_pb2_grpc as SoilLayerCurveDataService_pb2_grpc
|
|
18
|
+
from rsseismic.types import DiscretePoint, DynamicsPoint, GqHFittingLimits
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def _resolve_fitting_procedure(
|
|
22
|
+
fitting_procedure: FittingProcedureModel | str,
|
|
23
|
+
) -> str:
|
|
24
|
+
if isinstance(fitting_procedure, FittingProcedureModel):
|
|
25
|
+
return fitting_procedure.value
|
|
26
|
+
return fitting_procedure
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
class SoilLayerCurve(_ProxyObject):
|
|
30
|
+
"""
|
|
31
|
+
Fitted dynamics and discrete-point curve data for a profile soil layer.
|
|
32
|
+
|
|
33
|
+
Access via ``layer.Curve`` on a ``SoilLayer`` from
|
|
34
|
+
``model.Profiles.getSoilLayer(layerId)``. This is separate from
|
|
35
|
+
``layer.ReferenceCurve``, which holds the reference curve used as input
|
|
36
|
+
for curve fitting.
|
|
37
|
+
|
|
38
|
+
Use ``getDynamics`` / ``setDynamics`` for the fitted layer curve, or
|
|
39
|
+
``getDiscretePoints`` / ``setDiscretePoints`` when the layer uses a
|
|
40
|
+
discrete-points soil model. Call ``runCurveFit`` to fit dynamics from
|
|
41
|
+
the layer's reference curve.
|
|
42
|
+
"""
|
|
43
|
+
|
|
44
|
+
def __init__(self, client: Client, modelId: str, layerId: str):
|
|
45
|
+
super().__init__(client, layerId)
|
|
46
|
+
self.modelId = modelId
|
|
47
|
+
self._service = SoilLayerCurveDataService_pb2_grpc.SoilLayerCurveDataServiceStub(
|
|
48
|
+
self._client.channel
|
|
49
|
+
)
|
|
50
|
+
|
|
51
|
+
@property
|
|
52
|
+
def layerId(self) -> str:
|
|
53
|
+
return self._objectId
|
|
54
|
+
|
|
55
|
+
def getDynamics(self) -> list[DynamicsPoint]:
|
|
56
|
+
"""Return fitted dynamics points for the layer."""
|
|
57
|
+
request = SoilLayerCurveDataService_pb2.GetSoilLayerDynamicsRequest(
|
|
58
|
+
projectId=self.modelId,
|
|
59
|
+
layerID=self._objectId,
|
|
60
|
+
)
|
|
61
|
+
response = self._client.callFunction(self._service.GetDynamics, request)
|
|
62
|
+
require_success(response, "GetDynamics")
|
|
63
|
+
return from_dynamics_points(list(response.points))
|
|
64
|
+
|
|
65
|
+
def setDynamics(self, points: list[DynamicsPoint]) -> None:
|
|
66
|
+
"""Set layer dynamics points."""
|
|
67
|
+
if not points:
|
|
68
|
+
raise ValueError("At least one dynamics point is required.")
|
|
69
|
+
request = SoilLayerCurveDataService_pb2.SetSoilLayerDynamicsRequest(
|
|
70
|
+
projectId=self.modelId,
|
|
71
|
+
layerID=self._objectId,
|
|
72
|
+
points=to_dynamics_points(points),
|
|
73
|
+
)
|
|
74
|
+
response = self._client.callFunction(self._service.SetDynamics, request)
|
|
75
|
+
require_success(response, "SetDynamics")
|
|
76
|
+
|
|
77
|
+
def getDiscretePoints(self) -> list[DiscretePoint]:
|
|
78
|
+
"""Return discrete curve points (equivalent-linear analysis, Points soil model)."""
|
|
79
|
+
request = SoilLayerCurveDataService_pb2.GetDiscretePointsRequest(
|
|
80
|
+
projectId=self.modelId,
|
|
81
|
+
layerID=self._objectId,
|
|
82
|
+
)
|
|
83
|
+
response = self._client.callFunction(self._service.GetDiscretePoints, request)
|
|
84
|
+
require_success(response, "GetDiscretePoints")
|
|
85
|
+
return from_discrete_points(list(response.points))
|
|
86
|
+
|
|
87
|
+
def setDiscretePoints(self, points: list[DiscretePoint]) -> None:
|
|
88
|
+
"""Set discrete curve points."""
|
|
89
|
+
if not points:
|
|
90
|
+
raise ValueError("At least one discrete point is required.")
|
|
91
|
+
request = SoilLayerCurveDataService_pb2.SetDiscretePointsRequest(
|
|
92
|
+
projectId=self.modelId,
|
|
93
|
+
layerID=self._objectId,
|
|
94
|
+
points=to_discrete_points(points),
|
|
95
|
+
)
|
|
96
|
+
response = self._client.callFunction(self._service.SetDiscretePoints, request)
|
|
97
|
+
require_success(response, "SetDiscretePoints")
|
|
98
|
+
|
|
99
|
+
def runCurveFit(
|
|
100
|
+
self,
|
|
101
|
+
fittingProcedure: FittingProcedureModel | str,
|
|
102
|
+
gqHLimits: Optional[GqHFittingLimits] = None,
|
|
103
|
+
) -> int:
|
|
104
|
+
"""
|
|
105
|
+
Fit layer dynamics from the layer's reference curve.
|
|
106
|
+
|
|
107
|
+
Args:
|
|
108
|
+
fittingProcedure: Fitting procedure, for example
|
|
109
|
+
``FittingProcedureModel.MRDF`` or ``FittingProcedureModel.MRDF_UIUC``.
|
|
110
|
+
gqHLimits: Optional G–H fitting limits for procedures that support them.
|
|
111
|
+
|
|
112
|
+
Returns:
|
|
113
|
+
The number of fitted points.
|
|
114
|
+
"""
|
|
115
|
+
request = SoilLayerCurveDataService_pb2.RunCurveFitRequest(
|
|
116
|
+
projectId=self.modelId,
|
|
117
|
+
layerID=self._objectId,
|
|
118
|
+
fittingProcedure=_resolve_fitting_procedure(fittingProcedure),
|
|
119
|
+
)
|
|
120
|
+
if gqHLimits is not None:
|
|
121
|
+
request.gq_h_limits.CopyFrom(to_gq_h_fitting_limits_proto(gqHLimits))
|
|
122
|
+
response = self._client.callFunction(self._service.RunCurveFit, request)
|
|
123
|
+
require_success(response, "RunCurveFit")
|
|
124
|
+
return response.pointCount
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from rsseismic._client import Client
|
|
4
|
+
from rsseismic._proto_converters import (
|
|
5
|
+
from_append_layers_response,
|
|
6
|
+
from_delete_layer_response,
|
|
7
|
+
from_insert_layer_response,
|
|
8
|
+
from_move_layer_response,
|
|
9
|
+
)
|
|
10
|
+
from rsseismic._proxyObject import _ProxyObject
|
|
11
|
+
from rsseismic._rpc_helpers import require_success
|
|
12
|
+
from rsseismic.generatedFiles import SoilLayerService_pb2 as SoilLayerService_pb2
|
|
13
|
+
from rsseismic.generatedFiles import SoilLayerService_pb2_grpc as SoilLayerService_pb2_grpc
|
|
14
|
+
from rsseismic.types import AppendLayersResult, DeleteLayerResult, InsertLayerResult, MoveLayerResult
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class SoilLayerOperations(_ProxyObject):
|
|
18
|
+
"""Structural soil layer operations (insert, delete, duplicate, move) for an open model."""
|
|
19
|
+
|
|
20
|
+
def __init__(self, client: Client, modelId: str):
|
|
21
|
+
super().__init__(client, modelId)
|
|
22
|
+
self._soil_layer_service = SoilLayerService_pb2_grpc.SoilLayerServiceStub(self._client.channel)
|
|
23
|
+
|
|
24
|
+
def insertLayerBefore(
|
|
25
|
+
self,
|
|
26
|
+
referenceLayerID: str,
|
|
27
|
+
profileName: str = "",
|
|
28
|
+
) -> InsertLayerResult:
|
|
29
|
+
"""
|
|
30
|
+
Insert a soil layer before referenceLayerID.
|
|
31
|
+
|
|
32
|
+
referenceLayerID may be a soil layer GUID or the bedrock layer GUID.
|
|
33
|
+
"""
|
|
34
|
+
request = SoilLayerService_pb2.InsertLayerBeforeRequest(
|
|
35
|
+
modelID=self._objectId,
|
|
36
|
+
profileName=profileName,
|
|
37
|
+
referenceLayerID=referenceLayerID,
|
|
38
|
+
)
|
|
39
|
+
response = self._client.callFunction(self._soil_layer_service.InsertLayerBefore, request)
|
|
40
|
+
require_success(response, "InsertLayerBefore")
|
|
41
|
+
return from_insert_layer_response(response)
|
|
42
|
+
|
|
43
|
+
def insertLayerAfter(
|
|
44
|
+
self,
|
|
45
|
+
referenceLayerID: str,
|
|
46
|
+
profileName: str = "",
|
|
47
|
+
) -> InsertLayerResult:
|
|
48
|
+
"""Insert a soil layer after a soil layer GUID."""
|
|
49
|
+
request = SoilLayerService_pb2.InsertLayerAfterRequest(
|
|
50
|
+
modelID=self._objectId,
|
|
51
|
+
profileName=profileName,
|
|
52
|
+
referenceLayerID=referenceLayerID,
|
|
53
|
+
)
|
|
54
|
+
response = self._client.callFunction(self._soil_layer_service.InsertLayerAfter, request)
|
|
55
|
+
require_success(response, "InsertLayerAfter")
|
|
56
|
+
return from_insert_layer_response(response)
|
|
57
|
+
|
|
58
|
+
def appendLayers(
|
|
59
|
+
self,
|
|
60
|
+
count: int = 1,
|
|
61
|
+
profileName: str = "",
|
|
62
|
+
) -> AppendLayersResult:
|
|
63
|
+
"""Append one or more soil layers before bedrock."""
|
|
64
|
+
request = SoilLayerService_pb2.AppendLayersRequest(
|
|
65
|
+
modelID=self._objectId,
|
|
66
|
+
profileName=profileName,
|
|
67
|
+
count=count,
|
|
68
|
+
)
|
|
69
|
+
response = self._client.callFunction(self._soil_layer_service.AppendLayers, request)
|
|
70
|
+
require_success(response, "AppendLayers")
|
|
71
|
+
return from_append_layers_response(response)
|
|
72
|
+
|
|
73
|
+
def deleteLayer(
|
|
74
|
+
self,
|
|
75
|
+
layerID: str,
|
|
76
|
+
profileName: str = "",
|
|
77
|
+
) -> DeleteLayerResult:
|
|
78
|
+
"""Delete a soil layer. At least one soil layer must remain."""
|
|
79
|
+
request = SoilLayerService_pb2.DeleteLayerRequest(
|
|
80
|
+
modelID=self._objectId,
|
|
81
|
+
profileName=profileName,
|
|
82
|
+
layerID=layerID,
|
|
83
|
+
)
|
|
84
|
+
response = self._client.callFunction(self._soil_layer_service.DeleteLayer, request)
|
|
85
|
+
require_success(response, "DeleteLayer")
|
|
86
|
+
return from_delete_layer_response(response)
|
|
87
|
+
|
|
88
|
+
def duplicateLayer(
|
|
89
|
+
self,
|
|
90
|
+
sourceLayerID: str,
|
|
91
|
+
insertAfter: bool = True,
|
|
92
|
+
profileName: str = "",
|
|
93
|
+
) -> InsertLayerResult:
|
|
94
|
+
"""Duplicate a soil layer before or after the source layer."""
|
|
95
|
+
request = SoilLayerService_pb2.DuplicateLayerRequest(
|
|
96
|
+
modelID=self._objectId,
|
|
97
|
+
profileName=profileName,
|
|
98
|
+
sourceLayerID=sourceLayerID,
|
|
99
|
+
insertAfter=insertAfter,
|
|
100
|
+
)
|
|
101
|
+
response = self._client.callFunction(self._soil_layer_service.DuplicateLayer, request)
|
|
102
|
+
require_success(response, "DuplicateLayer")
|
|
103
|
+
return from_insert_layer_response(response)
|
|
104
|
+
|
|
105
|
+
def moveLayer(
|
|
106
|
+
self,
|
|
107
|
+
layerID: str,
|
|
108
|
+
newIndex1Based: int,
|
|
109
|
+
profileName: str = "",
|
|
110
|
+
) -> MoveLayerResult:
|
|
111
|
+
"""Move a soil layer to a new 1-based index."""
|
|
112
|
+
request = SoilLayerService_pb2.MoveLayerRequest(
|
|
113
|
+
modelID=self._objectId,
|
|
114
|
+
profileName=profileName,
|
|
115
|
+
layerID=layerID,
|
|
116
|
+
newIndex1Based=newIndex1Based,
|
|
117
|
+
)
|
|
118
|
+
response = self._client.callFunction(self._soil_layer_service.MoveLayer, request)
|
|
119
|
+
require_success(response, "MoveLayer")
|
|
120
|
+
return from_move_layer_response(response)
|