Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
a0aadea
Create new easyscience base class and model_base class.
damskii9992 Nov 19, 2025
952da19
Constraints serialization (#148)
rozyczko Nov 19, 2025
b5169f5
Interface factory unit tests (#151)
rozyczko Nov 19, 2025
66a199e
Pr comments
damskii9992 Nov 20, 2025
690ce16
Simplify NewBase from_dict class using `_is_serialized_easyscience_ob…
damskii9992 Nov 20, 2025
6687c33
Sort imports
damskii9992 Nov 21, 2025
5def3e7
Merge branch 'new_base_class' of https://github.com/EasyScience/EasyS…
damskii9992 Nov 21, 2025
884df73
Test new serializer_base methods except deserialize_dict
damskii9992 Nov 21, 2025
2ba98e7
Documentation (#152)
rozyczko Nov 24, 2025
0f8b932
Last new SerializerBase test
damskii9992 Nov 24, 2025
8e3ab34
Create new easyscience base class and model_base class.
damskii9992 Nov 19, 2025
51100ad
Sort imports
damskii9992 Nov 21, 2025
4189a27
Pr comments
damskii9992 Nov 20, 2025
64f09c1
Simplify NewBase from_dict class using `_is_serialized_easyscience_ob…
damskii9992 Nov 20, 2025
4e26dfc
Test new serializer_base methods except deserialize_dict
damskii9992 Nov 21, 2025
f396c70
Last new SerializerBase test
damskii9992 Nov 24, 2025
ef6ee5c
Merge branch 'new_base_class' of https://github.com/easyscience/corel…
damskii9992 Nov 24, 2025
d19f256
Add as_dict tests
damskii9992 Nov 24, 2025
cecc7b0
Format and Lint
damskii9992 Nov 24, 2025
fef80c0
Pixi update and final NewBase tests
damskii9992 Nov 24, 2025
390ad80
First ModelBase tests
damskii9992 Nov 24, 2025
113ec7c
Changes from ADR discussions
damskii9992 Nov 25, 2025
e10a4fa
Test all get_variable methods
damskii9992 Nov 25, 2025
99c83e3
Fix to recursive encoder to work with new base classes and nested des…
damskii9992 Nov 25, 2025
5e67822
Final ModelBase unit tests
damskii9992 Nov 25, 2025
f8b0817
Add minimization integration test with ModelBase
damskii9992 Nov 25, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 0 additions & 10 deletions .github/workflows/ossar-analysis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -26,16 +26,6 @@ jobs:
- run: git checkout HEAD^2
if: ${{ github.event_name == 'pull_request' }}

# Ensure a compatible version of dotnet is installed.
# The [Microsoft Security Code Analysis CLI](https://aka.ms/mscadocs) is built with dotnet v3.1.201.
# A version greater than or equal to v3.1.201 of dotnet must be installed on the agent in order to run this action.
# Remote agents already have a compatible version of dotnet installed and this step may be skipped.
# For local agents, ensure dotnet version 3.1.201 or later is installed by including this action:
# - name: Install .NET
# uses: actions/setup-dotnet@v1
# with:
# dotnet-version: '3.1.x'

# Run open source static analysis tools
- name: Run OSSAR
uses: github/ossar-action@v1
Expand Down
17 changes: 9 additions & 8 deletions .github/workflows/python-ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -65,15 +65,16 @@ jobs:
- name: Install dependencies and run tests
run: pixi run -e dev test

- name: Upload coverage
uses: codecov/codecov-action@v4.0.1
- name: Upload coverage reports to Codecov
uses: codecov/codecov-action@v5
with:
token: ${{ secrets.CODECOV_TOKEN_NEW }}
name: Pytest coverage
env_vars: OS,PYTHON,GITHUB_ACTIONS,GITHUB_ACTION,GITHUB_REF,GITHUB_REPOSITORY,GITHUB_HEAD_REF,GITHUB_RUN_ID,GITHUB_SHA,COVERAGE_FILE
env:
OS: ${{ matrix.os }}
PYTHON: ${{ matrix.python-version }}
name: unit-tests-job
flags: unittests
files: ./coverage-unit.xml
fail_ci_if_error: true
verbose: true
token: ${{ secrets.CODECOV_TOKEN }}
slug: EasyScience/corelib

Package_Testing:

Expand Down
6 changes: 6 additions & 0 deletions Examples/fitting/README.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
.. _fitting_examples:

Fitting Examples
----------------

This section gathers examples which demonstrate fitting functionality using EasyScience's fitting capabilities.
216 changes: 216 additions & 0 deletions PARAMETER_DEPENDENCY_SERIALIZATION.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,216 @@
# Parameter Dependency Serialization

This document explains how to serialize and deserialize `Parameter` objects that have dependencies.

## Overview

Parameters with dependencies can now be serialized to dictionaries (and JSON) while preserving their dependency relationships. After deserialization, the dependencies are automatically reconstructed using the `serializer_id` attribute to match parameters, with `unique_name` attribute being used as a fallback.

## Key Features

- **Automatic dependency serialization**: Dependency expressions and maps are automatically saved during serialization
- **Reliable dependency resolution**: Dependencies are resolved using stable `serializer_id` attributes with `unique_name` as fallback after deserialization
- **Order-independent loading**: Parameters can be loaded in any order thanks to the reliable ID system
- **Bulk dependency resolution**: Utility functions help resolve all dependencies at once
- **JSON compatibility**: Full support for JSON serialization/deserialization
- **Backward compatibility**: Existing code using `unique_name` continues to work as fallback

## Usage

### Basic Serialization/Deserialization

```python
import json
from easyscience import Parameter, global_object
from easyscience.variable.parameter_dependency_resolver import resolve_all_parameter_dependencies

# Create parameters with dependencies
a = Parameter(name="a", value=2.0, unit="m", min=0, max=10)
b = Parameter.from_dependency(
name="b",
dependency_expression="2 * a",
dependency_map={"a": a},
unit="m"
)

# Serialize to dictionary and save to file
params_dict = {"a": a.as_dict(), "b": b.as_dict()}
with open("parameters.json", "w") as f:
json.dump(params_dict, f, indent=2, default=str)

print("Parameters saved to parameters.json")
```

In a new Python session:

```python
import json
from easyscience import Parameter, global_object
from easyscience.variable.parameter_dependency_resolver import resolve_all_parameter_dependencies

# Load parameters from file
with open("parameters.json", "r") as f:
params_dict = json.load(f)

# Clear global map (simulate new environment)
global_object.map._clear()

# Deserialize parameters
new_a = Parameter.from_dict(params_dict["a"])
new_b = Parameter.from_dict(params_dict["b"])

# Resolve dependencies
resolve_all_parameter_dependencies({"a": new_a, "b": new_b})

# Dependencies are now working
new_a.value = 5.0
print(new_b.value) # Will be 10.0 (2 * 5.0)
```

### JSON Serialization

```python
import json

# Serialize to JSON
param_dict = parameter.as_dict()
json_str = json.dumps(param_dict, default=str)

# Deserialize from JSON
loaded_dict = json.loads(json_str)
new_param = Parameter.from_dict(loaded_dict)

# Resolve dependencies
resolve_all_parameter_dependencies(new_param)
```

### Bulk Operations

```python
from easyscience.variable.parameter_dependency_resolver import get_parameters_with_pending_dependencies

# Create multiple parameters with dependencies
params = create_parameter_hierarchy() # Your function

# Serialize all
serialized = {name: param.as_dict() for name, param in params.items()}

# Clear and deserialize
global_object.map._clear()
new_params = {name: Parameter.from_dict(d) for name, d in serialized.items()}

# Check which parameters have pending dependencies
pending = get_parameters_with_pending_dependencies(new_params)
print(f"Found {len(pending)} parameters with pending dependencies")

# Resolve all at once
resolve_all_parameter_dependencies(new_params)
```

## Implementation Details

### Serialization

During serialization, the following additional fields are added to dependent parameters:

- `_dependency_string`: The original dependency expression
- `_dependency_map_serializer_ids`: A mapping of dependency keys to stable dependency IDs (preferred)
- `_dependency_map_unique_names`: A mapping of dependency keys to unique names (fallback)
- `__serializer_id`: The parameter's own unique dependency ID
- `_independent`: Boolean flag indicating if the parameter is dependent

### Deserialization

During deserialization:

1. Parameters are created normally but marked as independent temporarily
2. Dependency information is stored in `_pending_dependency_string`, `_pending_dependency_map_serializer_ids`, and `_pending_dependency_map_unique_names` attributes
3. The parameter's own `__serializer_id` is restored from serialized data
4. After all parameters are loaded, `resolve_all_parameter_dependencies()` establishes the dependency relationships using dependency IDs first, then unique names as fallback

### Dependency Resolution

The dependency resolution process:

1. Scans for parameters with pending dependencies
2. First attempts to look up dependency objects by their stable `serializer_id`
3. Falls back to `unique_name` lookup in the global map if serializer_id is not available
4. Calls `make_dependent_on()` to establish the dependency relationship
5. Cleans up temporary attributes

This dual-strategy approach ensures reliable dependency resolution regardless of parameter loading order while maintaining backward compatibility.

## Error Handling

The system provides detailed error messages for common issues:

- Missing dependencies (parameter with required unique_name not found)
- Invalid dependency expressions
- Circular dependency detection

## Utility Functions

### `resolve_all_parameter_dependencies(obj)`

Recursively finds all Parameter objects with pending dependencies and resolves them.

**Parameters:**
- `obj`: Object to search for Parameters (can be Parameter, list, dict, or complex object)

**Returns:**
- None (modifies parameters in place)

**Raises:**
- `ValueError`: If dependency resolution fails

### `get_parameters_with_pending_dependencies(obj)`

Finds all Parameter objects that have pending dependencies.

**Parameters:**
- `obj`: Object to search for Parameters

**Returns:**
- `List[Parameter]`: List of parameters with pending dependencies

## Best Practices

1. **Always resolve dependencies after deserialization**: Use `resolve_all_parameter_dependencies()` after loading serialized parameters

2. **Handle the global map carefully**: The global map must contain all referenced parameters for dependency resolution to work

3. **Use unique names for cross-references**: When creating dependency expressions that reference other parameters, consider using unique names with quotes: `'Parameter_0'`

4. **Error handling**: Wrap dependency resolution in try-catch blocks for robust error handling

5. **Bulk operations**: For complex object hierarchies, use the utility functions to handle all parameters at once

6. **Reliable ordering**: With the new dependency ID system, parameters can be loaded in any order without affecting dependency resolution

7. **Access dependency ID**: Use `parameter.serializer_id` to access the stable ID for debugging or manual cross-referencing

## Example: Complex Hierarchy

```python
def save_model(model):
\"\"\"Save a model with parameter dependencies to JSON.\"\"\"
model_dict = model.as_dict()
with open('model.json', 'w') as f:
json.dump(model_dict, f, indent=2, default=str)

def load_model(filename):
\"\"\"Load a model from JSON and resolve dependencies.\"\"\"
global_object.map._clear() # Start fresh

with open(filename) as f:
model_dict = json.load(f)

model = Model.from_dict(model_dict)

# Resolve all parameter dependencies
resolve_all_parameter_dependencies(model)

return model
```

This system ensures that complex parameter hierarchies with dependencies can be reliably serialized and reconstructed while maintaining their behavioral relationships.
Loading
Loading