# NICQS
**No-Insulation Coil Quench Simulator**

*Thermo-electromagnetic modeling tool for simulating transients in No-Insulation (NI) HTS magnets using a 2D homogenized approach.*

[![Python Version](https://img.shields.io/badge/python-3.10%20%7C%203.11-blue)](https://www.python.org/)
[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)

## Installation

### From PyPI (Recommended)
```bash
pip install nicqs
```

For development dependencies:
```bash
pip install nicqs[dev]
```

### From Source
**Prerequisites:**
- Python 3.10 or 3.11
- C compiler:
  - Windows: Microsoft C++ Build Tools (https://visualstudio.microsoft.com/visual-cpp-build-tools/)
  - Linux/Mac: GCC compiler

**Installation steps:**
```bash
# Clone the repository
git clone https://gitlab.cern.ch/steam/ni-coils.git
cd ni-coils

# Install in editable mode
pip install -e .

# Or install with all optional dependencies
pip install -e .[all]
```

**PyTorch Installation (Required):**

NICQS requires PyTorch for the solver. Install the appropriate version for your system:
```bash
# CPU version (recommended for most users)
pip install torch

# CUDA 11.8 (for NVIDIA GPUs - faster computation)
pip install torch --index-url https://download.pytorch.org/whl/cu118

# For other CUDA versions, visit: https://pytorch.org/get-started/locally/
```

**Note:** PyTorch is not included as a dependency because the installation method varies by system (CPU vs GPU). You must install it separately.

## Quick Start

### Running a simulation
1. Create an input YAML file that defines the coil geometry
2. Run the geometry preprocessor:
   ```bash
   nicqs-geometry path/to/geometry.yaml output_directory
   ```
3. An output folder will be created with all geometry-specific information
4. Edit the generated `solver_input.yaml` file as needed
5. Run the simulation:
   ```bash
   nicqs-solver path/to/solver_input.yaml
   ```

### Python API Usage
```python
from nicqs.geometry import NI_coil_geometry
from nicqs.solver_torch import NI_Coil_Solver

# Create geometry from input YAML
geom = NI_coil_geometry(
    input_yaml_path="path/to/geometry.yaml",
    output_path="output_directory"
)
geom.create_geometry()

# Run solver with generated solver_input.yaml
solver = NI_Coil_Solver(input_yaml_path="output_directory/solver_input.yaml")
solver.run()
```


## Example input for the geometry creation.

```yaml
# Geometry yaml input parameters
inner_radius: [0.03, 0.03] # List of inner radii [m]
outer_radius: [0.06, 0.08] # List of outer radii [m]
thickness: [0.012, 0.012] # List of pancake thicknesses [m] (commondly the width of the HTS tape)
offset: [0.0, 0.0] # List with offsets along the z-axis [m]. OPTIONAL, default=0
repeat: [2, 2] # List of how many of these pancake coils are generated along the z-axis [-]. OPTIONAL, default=1
spacing: [0.002, 0.030] # List of spacing between pancake coils [m], only needed if the coil element will be repeated more than once. OPTIONAL, default=0
parallel: [5, 2] # List of number of parallel elements [-], needed for screening currents. E.g. if parallel is set to 5, the tape will be subdivided in to 5 elements over its width. OPTIONAL, default=1
serial: [30, 50] # List of number of subdivisions per coil elements [-]. E.g. if a pancake coil has 100 turns and serial is set to 10, it will be subdivided into 10 blocks of 10 tapes. OPTIONAL, default=1
turns: [300, 50] # List of turns per pancake coil [-]. If the material is normal conducting, it is better to have the amount of turns equal to the amount of serial connections.
circuit: [1] # List of circuit numbers of the coils [-]. Circuit 0 is not powered, any number afterwards can be powered.

# Operational parameters:
I_initial: [100.0, 0.0] # List of operating currents [A]. This is just for field-plot generation, can be changed during simulation. OPTIONAL, default=1
T_initial: [20.0, 20.0] # List of operating temperatures [K]. OPTIONAL, default=5

# There are several way to add a characteristic time to the magnets: 
# Please choose the most convenient option, if multiple options are selected it will choose the option based on a priority list: "tau_circuit" > "resistivity_circuit" > "tau" > "resistivity"
tau: [20.0, 0.0] # List of magnet characteristic times [s]. OPTIONAL, default=1
tau_circuit: {1: 20} # Dictionary for the circuit, R_circuit = L_circuit / Tau_circuit
resistivity: 0.00001 # Float or List [0.00001, 0.00001]
resistivity_circuit: {1: 0.00001} # Dictionary for the circuit

T_ref: [20.0, 0.0] # Reference temperature for the characteristic time [K]. OPTIONAL, default=5
B_ref: [5.0, 0.0] # Reference magnetic field for the characteristic time [T]. OPTIONAL, default=5
conductor: ['SC1', 'Cu'] # Conductor name [str]. OPTIONAL, default='Dummy'

# Conductor properties:
Conductor_type                      : {'SC1': 'Superconductor', 'Cu': 'Normal'}
Conductor_SC_properties             : {'SC1': {'fit-name': 'CFUN_HTS_JcFit_Fujikura_v1', 'n-value': 15, 'Ic': 1500.0, 'Tref': 4.5, 'Bref': 10, 'angle': 0.0, 'parallel': 5}}
Conductor_materials                 : {'SC1': ['Cu100', 'Hastelloy', 'Silver'], 'Cu': ['Cu100']}
Conductor_materials_width           : {'SC1': 12e-3, 'Cu': 12e-3}
Conductor_materials_thickness       : {'SC1': [20e-6, 50e-6, 2e-6], 'Cu': 1e-3}
Conductor_resistor                  : {'SC1': 'Cu100', 'Cu': 'Cu100'}
Conductor_conductivity_materials    : {'SC1': ['Cu100', 'Hastelloy'], 'Cu': ['Cu100']}
Conductor_conductivity_thickness    : {'SC1': 0.000050, 'Cu': 0.001}
Conductor_conductivity_width        : {'SC1': {'Cu100': 0.01e-3, 'Hastelloy': 11.99e-3}, 'Cu:': {'Cu100': 12e-3}}

# Plotting options:
inductance_calculation: 'analytic' # 'discrete' or 'analytic', the last one is default, faster and better.
rotate_90: false # rotates the plot 90 degrees.
mirror: true # mirrors the plot, otherwise it plots only half the magnet.
plot_lines: 'outer' # 'outer' or 'all', outer plots the outlines of the pancakes, all plots the outlines of each element.

# Magnetic field
xmin:           0.0
xmax:           0.1
ymin:           -0.1
ymax:           0.1
nx:             100
ny:             100
```

The yaml file above will produce the following geometry:
<p align="center">
    <img src="documentation/images/doc_yaml_field.png" width="640">

</p>

## Solver Configuration

After running `nicqs-geometry`, two configuration files are automatically created in the output folder:
- **`solver_input.yaml`** - Defines initial conditions, circuit data, and simulation parameters
- **`solver_settings.yaml`** - Configures solver tolerances and integration parameters

You should review and modify these files according to your simulation needs. Below is a reference for the key parameters in each file.

### solver_input.yaml Parameters
```yaml
circuit_data: # dictionary with a number of positions that corresponds to the number of circuits that we have in our model. In each position/circuit we need to define the dI/dt rate, and the corresponding time (another 2 positions).
  circuit_name:
    dIdt:
    - 0.0
    - 0.0
    t:
    - 0
    - 1

initial_conditions: # dictionary with a number of positions that correspond to the number of different pieces in our model. In each position we have to define the keys:
  coil_nr:
    file_path: '' # the output file with the results we will use
    I_R: 0.0 # the initial current for the resistor
    I_ind: 0.0 # the initial current of the inductor
    Line_index: -1 # which line of the input file it used as input, -1 is the last one.
    Load_from_file: False # boolean type, set true to load data from the file with the "file_path" path.
    T: 4.5 # Initial temperature, K

path_dict: # This is a dictionary that has some information on which paths, geometry and solver files should be used.
  geometry_yaml_path: geometry\output\magnet_folder\geometry.yaml
  solver_settings_path: geometry\output\magnet_folder\solver_settings.yaml
  specific_output_path: geometry\output\Fmagnet_folder

# Common input parameters:
thermal_timer: 99999 # initial time for the thermal solver to start working, one can set it to a large number to include thermal & loss calculations, but without the temperature actually increasing.
sampling_time_custom:  [initial time, end time, step] # time range with a custom time step. There is still a chance for the solver to step over this range. It is better to add some timesteps in the circuit_data dictionary, even if you dont want any changes in dIdt.
```

    
    It is is important to note that the first time we run the solver script,we have to set file_path : ' ' and Load_from_file : false, so that we create the first simulation file. After that we are free to use the results of this simulation file by using its path as the file_path and by seting the Load_from_file variable to true.

*   **thermal_timer** : initial time for the thermal solver to start working
*   **sampling_time_custom** :  array in a format [initial time, end time, step], for the solver
*   **max_cooling** : boolean type. If set to true we have the maximum cooling possible and all excess heat will be cooled away.
*   **maximum_cooling_value** : It is the value above which, all excess heat will be cooled down.
*   **quench_fraction** : the percentage of the model getting affected
*   **heat_nodes** : array or scalar that indicates which nodes will be heated up
*   **heater_power** : the power applied to the nodes
*   **Cap_discharge** : boolean value indicating whether there is a discharge
*   **Cap_discharge_time** : time of discharge in seconds
*   **Dis_List_Coils** : array or scalar that indicates which coils are discharging
*   **R_multiplier** : Parameter with which we can modify the resistance R = L/tau

### solver_settings.yaml Parameters

*   **atol**: integration parameter (absolute tolerance)
*   **colormap_name**: coolwarm
*   **dt**: timestamp
*   **electrical_part** : option to enable the electrical part
*   **keep_smallest_timestep** : option to set as timestep the smallest value
*   **max_step**
*   **max_timestep** : maximum timestep value which limits the sampling_time_custom attribute
*   **min_step** : 
*   **min_timestep**
*   **rtol**
*   **sampling_time_dI**
*   **sampling_time_dT**
*   **solver**
*   **stop_criterion_E**
*   **stop_criterion_T**
*   **stop_on_E**
*   **stop_on_T**
*   **t0**
*   **thermal_part** : option to enable the thermal part

## Contributing

Contributions are welcome! Please feel free to submit issues or pull requests to the [GitLab repository](https://gitlab.cern.ch/steam/ni-coils).

## License

This project is licensed under the MIT License - see the LICENSE file for details.

## Citation

If you use NICQS in your research, please cite:
```
NICQS - No-Insulation Coil Quench Simulator
Tim Mulder, CERN
https://gitlab.cern.ch/steam/ni-coils
```

## Contact

**Author:** Tim Mulder
**Email:** tim.mulder@cern.ch